前端Vue项目——课程详情页面实现
一、详情页面路由跳转
应用 Vue Router 编程式导航通过 this.$router.push() 来实现路由跳转。
1、绑定查看详情事件
修改 src/components/Course/Course.vue 文件,给课程 div 绑定查看详情事件。
<div class="courseList"> <div class="detail" v-for="(course, index) in courseDetail" :key="course.id" @click="detailHandler(course.id)"> <div class="head"> <img :src="course.course_img" alt="" class="backImg"> <!-- 背景色:行内样式优先显示 --> <b class="mask" :style="{background: course.bgColor}"></b> <p>{{ course.name }}</p> </div>
v-on,缩写 @ ,绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。
2、定义详情事件
methods: { // 课程详情时间操作 detailHandler(id) { this.$router.push({ name: "course.detail", params: { detailId: id, } }) },
注意:path只能和query搭配使用,name可以和params/query搭配使用,使用params传参只能使用name进行引入。
3、定义新的路由规则
在 src/router/index.js 添加如下内容:
import CourseDetail from '@/components/Course/CourseDetail' // 配置路由规则 export default new Router({ linkActiveClass: 'is-active', mode: 'history', // 改为history模式 routes: [ // 略 // 课程详情,路由:course/detail/web/3 { path: '/course/detail/web/:detailId', // 动态匹配 name: 'course.detail', // 路由名称 component: CourseDetail // 对应组件 } ] })
要把某种模式匹配到的所有路由,全部映射到同个组件。比如这里是课程详情组件,但是各个不同的课程有不同的 id,且都要用这个组件来渲染。那么可以在vue-router的路由路径中使用”动态路径参数“(dynamic segment)来实现这个效果。
一个“路径参数”使用冒号 :
标记。当匹配到一个路由时,参数值会被设置到 this.$route.params
,可以在每个组件内使用。于是,我们可以更新 CourseDetail
的模板,输出当前课程的 ID:
<template> <div> CourseID:{{$route.params.detailId}} </div> </template> <script> export default { name: "CourseDetail", data() { return { }; }, }; </script>
页面显示效果如下所示:
二、课程详情页面实现
1、课程详情顶部布局和样式
<template> <div class="wrap"> <div class="web-course-banner"> <div class="container"> <div class="title"> <img src="../../../static/images/play.png" height="67" width="67" alt=""> <h1 class="course-title">Django框架学习</h1> </div> <span class="course-text">Python语言下最强大优秀的WEB框架从入门到进阶</span> <div class="course-list"> <ul> <li class="detail-item"> 难度:初级 </li> <li class="sep"></li> <li class = detail-item>时长:32小时</li> <li class="sep"></li> <li class = detail-item>学习人数:632人</li> <li class="sep"></li> <li class = detail-item>评分 10.0</li> </ul> </div> </div> </div> <!-- 代码略 --> </div> </template> <style lang="css" scoped> .wrap{ width: 100%; } .web-course-banner{ width: 100%; height: 512px; background: url(../../../static/images/web-banner.1402933.png) no-repeat; background-size: 100% 100%; text-align: center; overflow: hidden; } .container{ width: 1200px; margin: 182px auto 0; text-align: left; } .container img{ vertical-align: middle; } .container h1{ display: inline-block; font-size: 48px; color: #4a4a4a; letter-spacing: .37px; margin-left: 40px; font-family: PingFangSC-Light; font-weight: 500; line-height: 1.1; position: relative; top: 10px; } .course-text{ width: 464px; display: inline-block; font-size: 22px; color: #4a4a4a; letter-spacing: .17px; line-height: 36px; margin-top: 33px; } .course-list{ width: 100%; } .course-list ul{ margin-top: 63px; display: flex; align-items: center; justify-content: flex-start; } .course-list ul li.detail-item{ font-size: 18px; color: #4a4a4a; letter-spacing: .74px; height: 26px; padding: 0 20px; } .course-list ul li.sep{ width: 2px; height: 14px; border-left: 1px solid #979797; } </style>
显示效果如下所示:
2、课程详情顶部区域数据获取
上图中显示的数据都是写死在模板中的,需要通过axios从后端获取真实的数据信息。
(1)添加课程详情顶部接口
在封装axios的 api.js中添加新接口:
// 课程详情顶部数据 export const coursedetailtop = (courseid)=>{ return Axios.get('coursedetailtop/?courseid=${courseid}').then(res=>res.data); };
(2)发送请求获取数据及绑定数据
<script> export default { name: 'CourseDetail', data(){ return { // 声明变量存储数据 coursedetailtop: {}, // 课程顶部详情数据 } }, methods: { // 发送请求获取课程详情顶部数据 getCoursedetailtop(){ this.$http.coursedetailtop(this.$route.params.detailId) .then(res=>{ console.log(res); if(!res.error_no){ // 取非判断没有错误 this.coursedetailtop = res.data; } }) .catch(err=>{ console.log(err); }) } }, created() { this.getCoursedetailtop(); } }; </script>
查看控制台输出如下所示:
3、课程详情顶部数据渲染
<div class="web-course-banner"> <div class="container"> <div class="title"> <img src="../../../static/images/play.png" height="67" width="67" alt=""> <h1 class="course-title">{{coursedetailtop.name}}</h1> </div> <span class="course-text">{{coursedetailtop.course_solgan}}</span> <div class="course-list"> <ul> <li class="detail-item"> 难度:{{coursedetailtop.level}} </li> <li class="sep"></li> <li class = detail-item>时长:{{coursedetailtop.hours}}小时</li> <li class="sep"></li> <li class = detail-item>学习人数:{{ coursedetailtop.learnnumber }}人</li> <li class="sep"></li> <li class = detail-item>评分 {{ coursedetailtop.course_review }}li> </ul> </div> </div> </div>
显示效果如下所示:
4、课程详情接口适配luffy新接口
由于luffy网站更新,原接口已经关闭,因此更新API接口(api.js):
// 课程详情顶部数据 export const coursedetailtop = (courseid)=>{ return Axios.get(`course/${courseid}/top/`).then(res=>res.data); }; // 课程概述 export const coursedetail = (courseid)=>{ return Axios.get(`course/${courseid}/detail/`).then(res=>res.data); };
更新 CourseDetail.vue 数据绑定:
<div class="web-course-banner"> <div class="container"> <div class="title"> <img src="../../../static/images/play.png" height="67" width="67" alt=""> <h1 class="course-title">{{coursedetailtop.name}}</h1> </div> <span class="course-text">{{coursedetailtop.course_solgan}}</span> <div class="course-list"> <ul> <li class="detail-item"> 难度:{{coursedetailtop.level}} </li> <li class="sep"></li> <li class = detail-item>课程总时长:{{coursedetailtop.numbers}}课时/{{coursedetailtop.hours}}小时</li> <li class="sep"></li> <li class = detail-item>学习人数:{{coursedetailtop.learn_number}}人</li> </ul> </div> </div> </div> <div class="course-review"> <ul class="review-head-wrap"> <li class="head-item">课程概述</li> <li class="head-item">课程章节</li> <li class="head-item">用户评价({{coursedetailtop.review_number}})</li> <li class="head-item">常见问题</li> </ul> </div>
显示效果:
三、课程详情套餐区实现
1、更新课程详情返回数据
getCourseDetail(){ this.$http.coursedetail(this.$route.params.detailId) .then(res=>{ console.log(res); // this.content = res.data.content; this.coursedetail = res.data; }) .catch(err=>{ console.log(err); }) }
2、更新课程详情套餐区模板
<!-- 课程详情 --> <div class="course-detail"> <div class="container"> <!-- v-html会将元素当成HTML标签解析后输出 --> <section class="course_item" v-html="content"></section> </div> </div> <div class="course-price"> <div class="container"> <span>可以根据不同的学习情况购买不一样的学习套餐哦!</span> <ul class="course-price-item" > <li v-for="(item, index) in coursedetail.prices" :key="item.id"> <p class="price">¥{{item.price}}</p> <p class="time">有效期{{item.valid_period_name}}</p> </li> </ul> <div class="course-action"> <button class="left">购买</button> <button class="right">加入购物车</button> </div> </div> </div>
显示效果如下所示:
3、点选课程样式切换
点击对应课程有效期,样式和颜色切换。
(1)定义li标签样式
.course-price ul li{ width: 200px; height: 112px; border: 1px solid #979797; } .course-price ul li.active{ background: #00CD23; }
(2)动态绑定样式
给li标签条件绑定active类样式。同时添加点击事件:
<ul class="course-price-item" > <li v-for="(item, index) in coursedetail.prices" :key="item.id" :class="{active:index===currentIndex}" @click="priceClick(index)"> <p class="price">¥{{item.price}}</p> <p class="time">有效期{{item.valid_period_name}}</p> </li> </ul>
在script中添加currentIndex原始数据和click点击事件:
<script> export default { name: 'CourseDetail', data(){ return { // 声明变量存储数据 coursedetailtop: {}, // 课程顶部详情数据 content: "", // currentIndex: 0, // 为0时,页面刷新默认选择了第一项 currentIndex: null // 默认不选择 } }, methods: { priceClick(index){ this.currentIndex = index; }, // 代码略 }, created() { this.getCoursedetailtop(); this.getCourseDetail(); } }; </script>
点击效果如下所示:
4、通过dom修改点击项字体和颜色(不推荐)
ref 加在普通的元素上,可用 this.ref.name 获取到的是dom元素。
<ul class="course-price-item" > <li v-for="(item, index) in coursedetail.prices" :key="item.id" :class="{active:index===currentIndex}" @click="priceClick(index)"> <p class="price" ref="price">¥{{item.price}}</p> <p class="time" ref="time">有效期{{item.valid_period_name}}</p> </li> </ul>
通过js来操作dom:
methods: { priceClick(index){ this.currentIndex = index; this.$refs.price[index].style.color = '#fff'; this.$refs.time[index].style.color = '#fff'; },
5、绑定样式修改点击项字体和颜色
传给 v-bind:class
一个对象,以动态地切换 class :
<div class="course-price"> <div class="container"> <span>可以根据不同的学习情况购买不一样的学习套餐哦!</span> <ul class="course-price-item" > <li v-for="(item, index) in coursedetail.prices" :key="item.id" :class="{active:index===currentIndex}" @click="priceClick(index)"> <p class="price" :class="{active:index===currentIndex}">¥{{item.price}}</p> <p class="time" :class="{active:index===currentIndex}">有效期{{item.valid_period_name}}</p> </li> </ul> <div class="course-action"> <button class="left">购买</button> <button class="right">加入购物车</button> </div> </div> </div>
上面语法表示 active 这个 class 存在与否取决于数据属性 index===currentIndex 的true/false。
添加点击项字体的 active 样式:
.course-price ul li p.active { color: #fff; }
如此,点击后会将点击项字体从黑色变成白色,优化显示效果。
四、课程购物车实现
添加购物车时,需要判断是否点选了课程套餐,没有点选课程套餐应提示错误信息。还需要判断用户是否登录,若用户未登录需要跳转到登录页面。
1、购物车点击事件
在购物车标签中添加点击事件:
<div class="course-action"> <button class="left">购买</button> <button class="right" @click="addShopCart">加入购物车</button> </div>
添加addShopCart方法:
<script> export default { name: 'CourseDetail', data(){ return { // 声明变量存储数据 coursedetailtop: {}, // 课程顶部详情数据 content: "", // currentIndex: 0, // 为0时,页面刷新默认选择了第一项 currentIndex: null // 默认不选择 } }, methods: { // 加入购物车事件 addShopCart(){ if(this.currentIndex){ // 判断当前currentIndex是否有值 } else { } }, // 代码略 }; </script>
这里将 currentIndex的默认值从0修改为null,这是因为如果默认为0,则默认选择了第一项,无需再提示未选中套餐。修改为 null 后,则是默认没有选择,因此可以通过 this.currentIndex 来判定当前是否选中了套餐。
2、用户未选中套餐消息提示
在没有选择套餐时点击添加购物车提示错误消息。这里采用element组件中 Message消息提示 来实现错误提示。
methods: { // 加入购物车事件 addShopCart(){ if(this.currentIndex){ // 判断当前currentIndex是否有值 } else { // element组件错误提示 this.$message({ message: '您还没有选择要加入的套餐哦!', center: true // 使用 center 属性让文字水平居中 }); } },
Element 注册了一个$message
方法用于调用,Message 可以接收一个字符串或一个 VNode 作为参数,它会被显示为正文内容。
使用 center
属性让文字水平居中。
3、点击购物车进入登录页面
用户点击购物车需要优先判断是否是登录用户,如果没有登录先跳转到登录页面。
(1)利用html本地存储功能实现登录用户信息保存
用户名、密码保存,自动登录等,可以通过设置cookie实现,第一次登录网站后在本地计算机的中写入cookie,之后再次登录此网站查看cookie中现有的值,用cookie值进行网站登录即可。但是 cookie 不适合大量数据的存储,因为它们由每个对服务器的请求来传递,这使得 cookie 速度很慢而且效率也不高。
HTML5提供了一个此类问题比较方便的解决方案,就是localstorage。数据不是由每个服务器请求传递的,而是只有在请求时使用数据。它使在不影响网站性能的情况下存储大量数据成为可能。对于不同的网站,数据存储于不同的区域,并且一个网站只能访问其自身的数据。HTML5 使用 JavaScript 来存储和访问数据。
因此可以使用 window.localStorage.getItem('token') 来判断用户是否登录。
(2)使用vue编程式导航跳转
methods: { // 加入购物车事件 addShopCart(){ if(this.currentIndex){ // 判断当前currentIndex是否有值 if(window.localStorage.getItem('token')){ // 判断用户是否登录 // 添加到购物车 } else { // 跳转登录页面 // 使用编程式导航来跳转 this.$router.push({ name: 'Login', query: { // window.location 只读属性,返回一个 Location 对象,其中包含有关文档当前位置的信息 return_url: window.location.href, // 将当前页面地址作为查询参数 } }) } } else { // element组件错误提示 this.$message({ message: '您还没有选择要加入的套餐哦!', center: true // 使用 center 属性让文字水平居中 }); } },
在 Vue 实例内部,你可以通过 $router
访问路由实例。因此你可以调用 this.$router.push。
(3)添加Login组件和路由
在index.js中引入Login组件并配置路由信息:
import Login from '@/components/Login/Login' Vue.use(Router) // 配置路由规则 export default new Router({ linkActiveClass: 'is-active', mode: 'history', // 改为history模式 routes: [ // 代码略 { path: '/login', name: 'Login', component: Login // Login 组件 } ] })
添加 src/components/Login/Login.vue 组件。
<template> <div class="box"> <img src="https://www.luffycity.com/static/img/Loginbg.3377d0c.jpg" alt=""> <div class="login"> <div class="login-title"> <img src="https://www.luffycity.com/static/img/Logotitle.1ba5466.png" alt=""> <p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p> </div> <div class="login_box"> <div class="title"> <span>密码登录</span> <span>短信登录</span> </div> <div class="inp"> <input v-model = 'username' type="text" placeholder="用户名 / 手机号码" class="user"> <input v-model = 'password' type="password" name="" class="pwd" placeholder="密码"> <div id="geetest"></div> <div class="rember"> <p> <input type="checkbox" class="no" name="a"></input> <span>记住密码</span> </p> <p>忘记密码</p> </div> <button class="login_btn">登录</button> <p class="go_login" >没有账号 <span>立即注册</span></p> </div> </div> </div> </div> </template> <script> export default { name: 'Login', data(){ return { username: "", password: "" } } }; </script>
显示效果如下所示: