阶段项目实战
目录
前言
美食广场项目开始
项目源码链接: 美食广场
提取码: 7f32
一、首页
- JS代码(css与html代码过多,暂不展示)
$(function(){ var url = 'https://serverms.xin88.top/index' $.get(url,data => { console.log(data); $('.topvideo>div>ul').html( data.hot_video.map(value => { const {mp4,pic,vname} = value return ` <li><video src="assets/video/${mp4}" preload="none" poster="assets/img/${pic}"></video> <i></i> <span>${vname}</span> </li> ` }) ) $('.main>.hot-search>ul').html( data.today_hot.map(value => { const {name,emphasize} = value return ` <li class="${emphasize?'em':''}"><a href="?p=search&wd=${name}">${name}</a></li> ` }) ) $('.main>.today-meal>.menu>ul').html( data.today_meal.map((value,index) => { const {cate_name,contents} = value // 用append累加到轮播组件中 $('.swiper-wrapper').append( contents.map(value => { const {pic,desc,title} = value return ` <div class="swiper-slide"> <div class="meal-item"> <img src="assets/img/food/${pic}" alt=""> <strong>${title}</strong> <span>${desc}</span> </div> </div> ` }) ) return ` <li class="${index == 0 ? 'active':''}">${cate_name}</li> ` }) ) // index-items $('.index-items').html( data.index_items.map(value => { const {title,items} = value // 将items装换成HTML const els = items.map(value => { const {author,desc,pic,title} = value return ` <li> <div> <img src="assets/img/food/${pic}" alt=""> <span>${author}</span> </div> <strong class="line-1">${title}</strong> <div> <i></i> <span class="line-1">${desc}</span> </div> </li> ` }) // join():把数组中的元素 拼接为字符串,参数为 拼时的间隔内容 return`<li> <h2>${title}</h2> <ul>${els.join('')}</ul> </li>` }) ) }) $('.topvideo>div>ul').on('click','li',function(){ // 判断当前激活项,是否已经具有 active样式 if($(this).hasClass('active')){ $(this).removeClass('active') $(this).siblings().removeClass('noactive') $(this).children('video').trigger('pause') }else{ // 当前li 下的video 标签 $(this).siblings().children().trigger('pause') $(this).children('video').trigger('play') // 未激活 宽度变小 $(this).addClass('active').siblings().removeClass('active') $(this).removeClass('noactive').siblings().addClass('noactive') } }) $('.main>.today-meal>.menu>ul').on('click','li',function(){ $(this).addClass('active').siblings().removeClass('active') mySwiper.slideTo($(this).index() * 3,1000,false) }) // 初始化swiper var mySwiper = new Swiper('.swiper',{ slidesPerView : 3, slidesPerGroup : 3,//每组三个 spaceBetween:10, autoplay: { delay: 3000, stopOnLastSlide: false, disableOnInteraction: true, }, on:{ slideChange:function(){ // 当前激活的滚动项 序号 $('.main>.today-meal>.menu>ul>li').eq(this.activeIndex / 3).click() } }, }) setInterval(() => { $('#banner').toggleClass('active') }, 4000); })
二、视频菜谱页
- JS代码
// 两个目的 // 1.确保代码在DOM元素即加载完毕后在执行 // 2.防止变量全局污染 jQuery || require('jquery') $(function(){ // JQuery || require('jquery') // 封装:因为分页操作 需要多次发送请求,然后生成新的UI;这段代码需要重复使用 function getData(pno){ var url = 'https://serverms.xin88.top/video?page=' + pno // 个人剧请求到的数据,生成列表的内容 $.get(url,data => { // 修改滚动条的位置到顶部 $(window).scrollTop(0) $('.list').html( data.data.map(value => { const {pic,views,duration,title} = value return `<li> <div> <img src="assets/img/video/${pic}" alt=""> <div> <span>${views}次播放</span> <span>${duration}</span> </div> </div> <span>${title}</span> </li> ` }) ) // 清空所有子元素 // 根据请求到的数据,生成分页内容 $('.pager>ul').empty() const {page,pageCount} = data // 场景:当前页18,则左侧起始页16 关系:-2 let start = Math.max(page -2,1) let end = start + 4 if(end >= pageCount){ start = Math.max(pageCount - 4,1) end = pageCount } for(let i = start;i <= end;i++){ $('.pager>ul').append(`<li class="${i == data.page ? 'active' : ''}">${i}</li>`) } // console.log(data) // 隐藏上一页 const $prevBtn = $('.pager>button').first() data.page == 1 ? $prevBtn.hide() : $prevBtn.show() // 隐藏下一页 const $nextBtn = $('.pager>button').last() data.page == data.pageCount ? $nextBtn.hide() : $nextBtn.show() }) } getData(1)//初始,请求第一页 // 为请求后产生的 动态新增子元素,添加事件--用委托 $('.pager>ul').on('click','li',function(){ const pno = $(this).text()//读取标签内容,即页数 getData(pno) }) // 下一页 $('.pager>button').last().on('click',function(){ const pno = $('.pager>ul>li.active').next().text() getData(pno) }) // 上一页 $('.pager>button').first().on('click',function(){ const pno = $('.pager>ul>li.active').prev().text() getData(pno) }) })
三、笔记页
- JS代码
$(function(){ let li_arr = [] //存放已经完成布局的li // 获取某个元素的 底部 距离 顶部的偏移量 function offset_bottom(el){ // 元素的高 + 元素样式top偏移量 // top是css的值,读取的是'..px' 需要转换成数字 才能相加 return $(el).height() + parseInt($(el).css('top')) } let = nowPage = 1 let lock = false function getData(pno){ var url = 'https://serverms.xin88.top/note?page=' + pno lock = true // 瀑布流思想:用定位方式,根据每个元素的实际宽和高,计算其摆放位置 // 而:图片是延时加载的,如果没有指定宽和高,那么只能等图片加载完毕 // 必须由服务器接口 提供图片的精准宽高 才能顺利完成 瀑布流 // 通过JS实现布局相关的 const space = 10//元素 const li_w = (1000 - 2 * space) / 3 // 等比例公式 img_h / img_w = height / width // li_h = height / width * img_w $.get(url,data => { // console.log(data); lock =false nowPage = data.page if(nowPage == data.pageCount){ $('.loadmore').text(`:-) 没有更多啦`).addClass('nomore') } $('.list').append( data.data.map(value => { const {cover,title,head_icon,name,favorite,height,width} = value const img_h = li_w * height / width return ` <li style="width:${li_w}px;"> <img style="height:${img_h}px;" src="assets/img/note/${cover}" alt=""> <p class="line-2">${title}</p> <div> <div> <img src="assets/img/note/${head_icon}" alt=""> <span>${name}</span> </div> <span>${favorite}</span> </div> </li> ` }) ) // 每次布局清空 li_arr li_arr = [] // 遍历添加的每个li元素,挨个进行布局 // each:是JQ提供的,遍历查询到的元素 $('.list>li').each((index,li) => { // 参数1:序号 参数2:元素 // console.log(index,li); // 前四个 并排摆放在最开头 if(index < 3){ $(li).css({top:0,left: index * (li_w + space)}) // 把完成布局的li,添加到数组 li_arr.push(li) }else{ // 假设 li_arr中,序号0的最小 var min_li = li_arr[0] li_arr.forEach(li => { // 如果 遍历到的li的 底部偏移量 比 之前设定的最小的 还要小 if(offset_bottom(li) < offset_bottom(min_li)){ // 最小值修改 min_li = li } }) // 新元素要摆放在 最小元素的 正下方 $(li).css({ left:$(min_li).css('left'), top:offset_bottom(min_li) + space }) // 调整存放已布局的元素数组,把最小的从里面删除,把新加入的添加进入 li_arr.push(li) // splice(序号,个数):从数组中 指定序号位置开始 删除 指定元素个数 const i = li_arr.indexOf(min_li)//获取最小元素的序号 li_arr.splice(i,1) } }) // 所有的li都是绝对定位 脱离文档流 导致其父元素的高度坍塌 var max_li = li_arr[0] li_arr.forEach(li => { if(offset_bottom(li) > offset_bottom(max_li)) max_li = li }) $('.list').css('height',offset_bottom(max_li)) }) } getData(1) $(window).on('scroll',function(){ var win_h = $(window).height() var st = $(window).scrollTop() var top = $('.loadmore').offset().top if(st > top - win_h && !lock && !$('.loadmore').hasClass('nomore') ){ getData(nowPage + 1 ) } }) })
四、商城页
- JS代码
$(function () { let nowPage = 1 let lock = false //锁: 初始状态-解锁 function getData(pno) { var url = 'https://serverms.xin88.top/mall?page=' + pno lock = true // 请求前: 锁定 $.get(url, data => { lock = false // 请求完: 解锁 console.log(data) //更新当前页数 nowPage = data.page if (nowPage == data.pageCount) { $('.loadmore').text("没有更多了").addClass('nomore') } // 列表内容 $('.list').append( data.data.map(value => { const { name, pic, price, sale_count } = value return `<li> <img src="assets/img/mall/${pic}" alt=""> <div> <p class="line-2">${name}</p> <div> <strong>¥${price}</strong> <span>月售${sale_count}</span> </div> </div> </li>` }) ) }) } getData(1) //初始化 $(window).on('scroll', function () { // 滚动量 var st = $(window).scrollTop() // 窗口高 var win_h = $(window).height() // 加载更多 的偏移量 var top = $('.loadmore').offset().top // 超过滚动极限值 并且 未锁定状态 并且 不是没有更多时 var nomore = $('.loadmore').hasClass('nomore') if (st >= top - win_h && !lock && !nomore) { console.log('加载..'); getData(nowPage + 1) } }) })
五、直播页
- JS代码
$(function () { let nowPage = 1 //存放当前页 // 应该记录 当前请求的加载状态, 如果请求中.. 就不应该请求下一次 // 防止请求被多次重复调用, 请求前锁门 let lock = false // 锁: 起始状态 未锁定 function getData(pno) { var url = `https://douyu.xin88.top/api/room/list?page=${pno}&type=ms` lock = true // 请求前: 锁门 $.get(url, data => { lock = false // 请求完: 解锁 console.log(data) //请求完毕后, 更新当前页 nowPage = data.data.nowPage // 如果最后一页: 则显示 没有更多数据了 if (nowPage == data.data.pageCount) { // nomore: 并非为了加样式, 而是 作为一个标识, 代表没有更多数据 // 在请求前: 判断 如果存在 nomore 样式, 就不要触发请求 $('.loadmore').text('没有更多了').addClass('nomore') } // html: 覆盖原有内容 // 新增: append $('.list').append( data.data.list.map(value => { const { hn, nickname, roomSrc, roomName } = value return `<li> <div> <img src="${roomSrc}" alt=""> <span class="hn">${hn}</span> <span class="nickname">${nickname}</span> </div> <span class="line-1">${roomName}</span> </li>` }) ) }) } getData(1) // 由于1页数据数量较少, 不足以充满整个页面 // 初始化时, 请求两次 getData(2) // 触 加载更多 监听 $(window).on('scroll', function () { // 窗口高 var win_h = $(window).height() // 滚动距离 var st = $(window).scrollTop() // 加载更多 元素距离顶部的偏移量 var top = $('.loadmore').offset().top // 滚动距离 大于 极限值(出现加载更多) // 额外条件: 非锁定状态 // 额外条件: 不应该存在nomore样式 if (st > top - win_h && !lock && !$('.loadmore').hasClass('nomore')) { console.log('请求更多数据...') // 当前页+1 getData(nowPage + 1) } }) })
六、搜索页
- JS代码
$(function () { // 给 排序方式 加点击事件 $(".area-sort>li").on('click', function () { $(this).addClass('active').siblings().removeClass('active') getData(1) }) function getData(pno) { // 自动回到顶部 $(window).scrollTop(0) // 请求类型: 与当前激活项的序号一致 var type = $('.area-sort>li.active').index() // 如果路径中, wd 没有值, 则用 参数2 作为默认值 var wd = $.s('wd', '') var url = `https://serverms.xin88.top/mall/search?type=${type}&kw=${wd}&page=${pno}` console.log(url) $.get(url, data => { console.log(data) $('.list').html( data.data.map(value => { const { name, pic, price, sale_count } = value return `<li> <img src="assets/img/mall/${pic}" alt=""> <div> <h3>${name}</h3> <strong>¥${price}</strong> <span>销量: ${sale_count}</span> </div> </li>` }) ) // 修改 上一页和 下一页的 不可用状态 var { page, pageCount } = data var $prev_btn = $('.pager>button').first() // 页数1, 则上一页不可用; 否则可用 page == 1 ? $prev_btn.addClass('disabled') : $prev_btn.removeClass('disabled') // 下一页 var $next_btn = $('.pager>button').last() // 动态获取方法名, 然后通过方括号语法 来找到此方法 执行 var m = (page == pageCount ? 'addClass' : 'removeClass') $next_btn[m]('disabled') // 生成分页 let start = page - 2 if (start < 1) start = 1 let end = start + 4 if (end > pageCount) end = pageCount start = end - 4 if (start < 1) start = 1 $('.pager>ul').empty() //清空旧的 for (let i = start; i <= end; i++) { $('.pager>ul').append(`<li class="${i == page ? 'active' : ''}">${i}</li>`) } }) } getData(1) $('.pager>ul').on('click', 'li', function () { var pno = $(this).text() getData(pno) }) // 下一页: $('.pager>button').last().on('click', function () { var pno = $('.pager>ul>li.active').next().text() getData(pno) }) $('.pager>button').first().on('click', function () { var pno = $('.pager>ul>li.active').prev().text() getData(pno) }) })
七、登录页
- JS代码
// login.js $(function () { var url = 'https://serverms.xin88.top/users/login' $('.area-login button').on('click', function () { var phone = $('.area-login input').eq(0).val() var pwd = $('.area-login input').eq(1).val() $.post(url, { phone, pwd }, data => { console.log(data); if (data.code == 200) { alert("登录成功! 即将跳转到首页") location.replace('?p=home') // 登录信息存储到浏览器 // 根据是否勾选 下次自动登录, 来决定存储的方式 长期还是短期 var checked = $('.area-login :checkbox').prop('checked') if (checked) { // 真: 长期 localStorage.setItem('user', JSON.stringify(data.data)) } else { // 假: 短期 sessionStorage.setItem('user', JSON.stringify(data.data)) } } else { alert(data.msg) } }) }) })
八、注册页
- JS代码
$(function () { // 手机号: 失去焦点后检测 $('.area-reg>.item:eq(0)>input') .on('blur', function () { var phone = $(this).val() //获取输入框的值 // 如果没有值, 则不做任何事情 if (phone == '') return // 手机号正则 if (!/^1[3-9]\d{9}$/.test(phone)) { $(this).addClass('err').next().show() } else { // POST : 发送手机号给服务器 查验是否已注册 var url = 'https://serverms.xin88.top/users/checkPhone' // POST与GET不同, 参数需要单独传递 // 参数: 往往会故意制造巧合 - 参数名 和 值的变量名一样 $.post(url, { phone }, data => { console.log(data); // 200: 手机号不存在, 说明可以注册 if (data.code == 200) { // 输入框下方 所有兄弟元素中, 带有 .ok 的 $(this).nextAll('.ok').show() } // 202: 手机号已注册 if (data.code == 202) { $(this).addClass('err').next().next().show() } }) } }) .on('focus', function () { // nextAll: 下方所有兄弟元素 $(this).removeClass('err').nextAll().hide() }) // 密码验证 $('.area-reg>.item:eq(1)>input') .on('focus', function () { $(this).removeClass('err').nextAll().hide() }) .on('blur', function () { var pwd = $(this).val() if (pwd == '') return if (pwd.length < 6 || pwd.length > 12) { $(this).addClass('err').next().show() } else { $(this).next().next().show() } }) // 预防: 如果有变更, 则再次验证 .on('change', function () { $('.area-reg>.item:eq(2)>input').focus().blur() }) // 再次验证 $('.area-reg>.item:eq(2)>input') .on('focus', function () { $(this).removeClass('err').nextAll().hide() }) .on('blur', function () { var re_pwd = $(this).val() var pwd = $('.area-reg>.item:eq(1)>input').val() if (re_pwd == '') return if (re_pwd == pwd) { $(this).next().next().show() } else { $(this).next().show() } }) // 注册 $('.area-reg>button').on('click', function () { // :checkbox 选中 type=checkbox 的标签 // prop: 获取标签的属性 var checked = $('.area-reg :checkbox').prop('checked') console.log('checked:', checked); //如果非勾选, 用抖动动画提示 if (checked) { // 把有错误的的输入框, 即代表 class='err' 的, 抖动提示 $('.area-reg input.err') .addClass('animate__shakeX animate__animated') // 如果有输入错误的输入框, 则只抖动提示, 不做后续处理 if ($('.area-reg input.err').length > 0) return // 如果 3个 span.ok 的元素都是可见的, 就说明都是正确的 // :visible 限定:可见的 const $oks = $('.area-reg span.ok:visible') console.log($oks) if ($oks.length == 3) { // 注册 var url = 'https://serverms.xin88.top/users/register' var phone = $('.area-reg input').eq(0).val() var pwd = $('.area-reg input').eq(1).val() $.post(url, { phone, pwd }, data => { console.log(data); if (data.code == 200) { //成功 alert(`恭喜您注册成功, 成为第${data.id}位注册用户, 即将跳转到登录页面`) location.replace('?p=login') } else { //不成功 alert(data.msg) } }) } else { // 报错 alert("请确保所有信息填写正确, 再进行注册") } } else { $(this).prev().addClass('animate__heartBeat') } }) // 动画完毕时, 要移除样式, 方便下次动画的执行 $('.area-reg>.agree').on('animationend', function () { $(this).removeClass('animate__heartBeat') }) $('.area-reg input').on('animationend', function () { $(this).removeClass('animate__shakeX') }) })
九、个人信息页
- JS代码
$(function () { $('.sidemenu li').on('click', function () { $(this).addClass('active').siblings().removeClass('active') var i = $(this).index() //点击项的序号 // 找到对应序号的内容 $('.content>li').eq(i).show().siblings().hide() }) // 初始化时, 显示个人信息 $('.content>li').eq(0).show() //退出 $('.content>li').last().children('button').on('click', function () { sessionStorage.removeItem('user') localStorage.removeItem('user') location.replace('?p=home') }) // 展示用户信息 var user = sessionStorage.getItem('user') || localStorage.getItem('user') user = JSON.parse(user) if (user.avatar) { $('.content>li:nth-child(2)>img').prop('src', user.avatar) } $('.phone').text(user.phone) // 时间戳: 转年月日 // 第三方库: moment.js 专门处理时间 // http://momentjs.cn/ $('.created').text(moment(user.created).format('YYYY-MM-DD HH:mm:ss')) // 所有头像的接口: var url_head = 'https://serverms.xin88.top/users/head_photos' $.get(url_head, data => { console.log(data) const baseURL = data.baseURL $('.head-photos').html( data.hero.map(value => { const { alias, selectAudio } = value const head_photo = baseURL.replace('${alias}', alias) return `<li> <img data-au="${selectAudio}" src="${head_photo}" alt=""> </li>` }) ) }) // 全局唯一的播放器 var audio = new Audio() // 为动态新增数据绑定事件 -- 委托 $('.head-photos').on('click', 'img', function () { var au = $(this).data('au') audio.src = au audio.play() // 替换图片 // 设置图片地址 为 当前点击的图片地址 $('.content>li:nth-child(2)>img').prop('src', $(this).prop('src')) }) // 点击确定, 发请求更新头像给用户 $('.content>li:nth-child(2)>button').on('click', function () { var url = 'https://serverms.xin88.top/users/head_photo' var id = user.id var alias = $('.content>li:nth-child(2)>img').prop('src') console.log({ id, alias }); $.post(url, { id, alias }, data => { console.log(data) if (data.code == 200) { alert("头像更新成功") $('.user>img').prop('src', alias) // 同时更新本地存储的用户信息中的头像 user.avatar = alias // 判断是临时存储 还是 长期存储, 更新对应数据 if (sessionStorage.getItem('user')) { sessionStorage.setItem('user', JSON.stringify(user)) } if (localStorage.getItem('user')) { localStorage.setItem('user', JSON.stringify(user)) } } else { alert(data.msg) } }) }) })
总结
10天项目实战