VUE前端
ProjectName/config/
dev.env.js
1 'use strict' 2 const merge = require('webpack-merge') 3 const prodEnv = require('./prod.env') 4 5 module.exports = merge(prodEnv, { 6 NODE_ENV: '"development"' 7 })
prod.env.js
1 'use strict' 2 module.exports = { 3 NODE_ENV: '"production"' 4 }
index.js
1 'use strict' 2 // Template version: 1.3.1 3 // see http://vuejs-templates.github.io/webpack for documentation. 4 5 const path = require('path') 6 7 module.exports = { 8 dev: { 9 10 // Paths 11 assetsSubDirectory: 'static', 12 assetsPublicPath: '/', 13 proxyTable: {}, 14 15 // Various Dev Server settings 16 host: 'www.luffycity.cn', // can be overwritten by process.env.HOST 17 port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 autoOpenBrowser: false, 19 errorOverlay: true, 20 notifyOnErrors: true, 21 poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 23 24 /** 25 * Source Maps 26 */ 27 28 // https://webpack.js.org/configuration/devtool/#development 29 devtool: 'cheap-module-eval-source-map', 30 31 // If you have problems debugging vue-files in devtools, 32 // set this to false - it *may* help 33 // https://vue-loader.vuejs.org/en/options.html#cachebusting 34 cacheBusting: true, 35 36 cssSourceMap: true 37 }, 38 39 build: { 40 // Template for index.html 41 index: path.resolve(__dirname, '../dist/index.html'), 42 43 // Paths 44 assetsRoot: path.resolve(__dirname, '../dist'), 45 assetsSubDirectory: 'static', 46 assetsPublicPath: '/', 47 48 /** 49 * Source Maps 50 */ 51 52 productionSourceMap: true, 53 // https://webpack.js.org/configuration/devtool/#production 54 devtool: '#source-map', 55 56 // Gzip off by default as many popular static hosts such as 57 // Surge or Netlify already gzip all static assets for you. 58 // Before setting to `true`, make sure to: 59 // npm install --save-dev compression-webpack-plugin 60 productionGzip: false, 61 productionGzipExtensions: ['js', 'css'], 62 63 // Run the build command with an extra argument to 64 // View the bundle analyzer report after build finishes: 65 // `npm run build --report` 66 // Set to `true` or `false` to always turn it on or off 67 bundleAnalyzerReport: process.env.npm_config_report 68 } 69 }
src/
settings.js
1 // src目录下创建settings.js站点开发配置文件 2 3 export default { 4 Host:"http://api.luffycity.cn:8000", 5 }
App.vue
1 <template> 2 <div id="app"> 3 <router-view/> 4 </div> 5 </template> 6 7 <script> 8 9 export default { 10 name: 'App', 11 components: { 12 } 13 } 14 </script> 15 16 <style> 17 18 </style>
main.js
1 // The Vue build version to load with the `import` command 2 // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 import Vue from 'vue' 4 import App from './App' 5 6 import router from './routers/index'; 7 import store from './store/index'; 8 9 // 手动的自定义全局配置 10 import settings from "./settings" 11 Vue.prototype.$settings = settings; 12 13 // elementUI 导入 14 import ElementUI from 'element-ui'; 15 import 'element-ui/lib/theme-chalk/index.css'; 16 // 调用插件 17 Vue.use(ElementUI); 18 19 import "../static/css/reset.css" 20 21 import axios from 'axios'; // 从node_modules目录中导入包 22 23 // 允许ajax发送请求时附带cookie 24 axios.defaults.withCredentials = true; 25 26 Vue.prototype.$axios = axios; // 把对象挂载vue中 27 28 29 Vue.config.productionTip = false; 30 31 // 导入gt极验 32 import '../static/js/gt.js'; 33 34 // vue-video视频播放插件 35 require('video.js/dist/video-js.css'); 36 require('vue-video-player/src/custom-theme.css'); 37 import VideoPlayer from 'vue-video-player' 38 Vue.use(VideoPlayer); 39 40 /* eslint-disable no-new */ 41 new Vue({ 42 el: '#app', 43 router, 44 store, 45 components: { App }, 46 template: '<App/>' 47 })
src/compoents/common
Header.vue
1 <template> 2 <div class="header"> 3 <el-container> 4 <el-header> 5 <el-row> 6 <el-col class="logo" :span="3"> 7 <a href="/"> 8 <img src="@/assets/head-logo.svg" alt=""> 9 </a> 10 </el-col> 11 <el-col class="nav" :span="16"> 12 <el-row> 13 <el-col :key="key" v-for="nav,key in nav_list" :span="3"><a :class="check(nav.link)?'current':''" :href="nav.link">{{nav.name}}</a></el-col> 14 </el-row> 15 </el-col> 16 <el-col class="login-bar" :span="5"> 17 <el-row v-if="token"> 18 <el-col class="cart-ico" :span="9"> 19 <router-link to=""> 20 <b class="goods-number">{{$store.state.cart.count}}</b> 21 <img class="cart-icon" src="@/assets/cart.svg" alt=""> 22 <span><router-link to="/cart">购物车</router-link></span> 23 </router-link> 24 </el-col> 25 <el-col class="study" :span="8" :offset="2"><router-link to="">学习中心</router-link></el-col> 26 <el-col class="member" :span="5"> 27 <el-menu class="el-menu-demo" mode="horizontal"> 28 <el-submenu index="2"> 29 <template slot="title"><router-link to=""><img src="@/assets/logo@2x.png" alt=""></router-link></template> 30 <el-menu-item index="2-1">我的账户</el-menu-item> 31 <el-menu-item index="2-2">我的订单</el-menu-item> 32 <el-menu-item index="2-3">我的优惠卷</el-menu-item> 33 <el-menu-item index="2-3"><span @click="logout">退出登录</span></el-menu-item> 34 </el-submenu> 35 </el-menu> 36 </el-col> 37 </el-row> 38 <el-row v-else> 39 <el-col class="cart-ico" :span="9"> 40 <router-link to=""> 41 <img class="cart-icon" src="@/assets/cart.svg" alt=""> 42 <span><router-link to="/cart">购物车</router-link></span> 43 </router-link> 44 </el-col> 45 <el-col :span="10" :offset="5"> 46 <span class="register"> 47 <router-link to="/login">登录</router-link> 48 | 49 <router-link to="/reg">注册</router-link> 50 </span> 51 </el-col> 52 </el-row> 53 </el-col> 54 </el-row> 55 </el-header> 56 </el-container> 57 </div> 58 </template> 59 60 <script> 61 export default { 62 name: "Header", 63 data(){ 64 return { 65 // 设置一个登录标识,表示是否登录 66 token: sessionStorage.token || localStorage.token, 67 user_name: sessionStorage.user_name || localStorage.user_name, 68 user_id: sessionStorage.user_id || localStorage.user_id, 69 nav_list:[], 70 }; 71 }, 72 created() { 73 // 获取导航 74 this.$axios.get(this.$settings.Host+"/nav/").then(response=>{ 75 this.nav_list = response.data 76 // console.log(this.nav_list) 77 }).catch(error=>{ 78 console.log(error.response) 79 }) 80 }, 81 methods:{ 82 check(link){ 83 return link==window.location.pathname 84 }, 85 logout(){ 86 87 this.token = false; 88 this.user_id=false; 89 this.user_name=false; 90 91 sessionStorage.removeItem("token"); 92 sessionStorage.removeItem("user_id"); 93 sessionStorage.removeItem("user_name"); 94 95 localStorage.removeItem("token"); 96 localStorage.removeItem("user_id"); 97 localStorage.removeItem("user_name"); 98 let _this = this; 99 _this.$alert('退出登录成功!', '路飞学城', { 100 callback(){ 101 _this.$router.push("/"); 102 } 103 }); 104 } 105 } 106 } 107 </script> 108 109 <style scoped> 110 .header{ 111 top:0; 112 left:0; 113 right:0; 114 margin: auto; 115 background-color: #fff; 116 height: 80px; 117 z-index: 1000; 118 position: fixed; 119 box-shadow: 0 0.5px 0.5px 0 #c9c9c9; 120 } 121 .header .el-container{ 122 width: 1200px; 123 margin: 0 auto; 124 } 125 .el-header{ 126 height: 80px!important; 127 padding:0; 128 } 129 .logo{ 130 131 } 132 .logo img{ 133 padding-top: 22px; 134 } 135 136 .nav{ 137 margin-top: 22px; 138 } 139 140 .nav .el-col a{ 141 display: inline-block; 142 text-align: center; 143 padding-bottom: 16px; 144 padding-left: 5px; 145 padding-right: 5px; 146 position: relative; 147 font-size: 16px; 148 margin-left: 20px; 149 } 150 151 .nav .el-col .current{ 152 color: #4a4a4a; 153 border-bottom: 4px solid #ffc210; 154 } 155 156 .login-bar{ 157 margin-top: 22px; 158 } 159 .cart-ico{ 160 position: relative; 161 border-radius: 17px; 162 } 163 .cart-ico:hover{ 164 background: #f0f0f0; 165 } 166 .goods-number{ 167 width: 16px; 168 height: 16px; 169 line-height: 17px; 170 font-size: 12px; 171 color: #fff; 172 text-align: center; 173 background: #fa6240; 174 border-radius: 50%; 175 transform: scale(.8); 176 position: absolute; 177 left: 16px; 178 top: -1px; 179 } 180 .cart-icon{ 181 width: 15px; 182 height: auto; 183 margin-left: 6px; 184 } 185 .cart-ico span{ 186 margin-left: 12px; 187 } 188 .member img{ 189 width: 26px; 190 height: 26px; 191 border-radius: 50%; 192 display: inline-block; 193 } 194 .member img:hover{ 195 border: 1px solid yellow; 196 } 197 198 </style>
Banner.vue
1 <template> 2 <div class="banner"> 3 <el-carousel trigger="click" height="473px"> 4 <el-carousel-item :key="key" v-for="banner,key in banner_list"> 5 <a :href="banner.link"><img width="100%" :src="banner.image" alt=""></a> 6 </el-carousel-item> 7 </el-carousel> 8 </div> 9 </template> 10 11 <script> 12 export default { 13 name:"Banner", 14 data(){ 15 return { 16 banner_list:[] 17 }; 18 }, 19 created() { 20 // 获取轮播图数据 21 this.$axios.get(this.$settings.Host+"/banner/").then(response=>{ 22 this.banner_list = response.data; 23 console.log(response.data); 24 }).catch(error=>{ 25 console.log(error.response); 26 }); 27 } 28 } 29 </script> 30 31 <style> 32 .el-carousel__arrow{ 33 width: 100px!important; 34 height: 100px!important; 35 } 36 .el-icon-arrow-left{ 37 font-size: 35px; 38 margin-left: 50px; 39 } 40 .el-carousel__arrow--left{ 41 left: -50px; 42 } 43 </style>
Footer.vue
1 <template> 2 <div class="footer"> 3 <el-container> 4 <el-row> 5 <el-col :span="4"><router-link to="">关于我们</router-link></el-col> 6 <el-col :span="4"><router-link to="">联系我们</router-link></el-col> 7 <el-col :span="4"><router-link to="">商务合作</router-link></el-col> 8 <el-col :span="4"><router-link to="">帮助中心</router-link></el-col> 9 <el-col :span="4"><router-link to="">意见反馈</router-link></el-col> 10 <el-col :span="4"><router-link to="">新手指南</router-link></el-col> 11 <el-col :span="24"><p class="copyright">Copyright © luffycity.com版权所有 | 京ICP备17072161号-1</p></el-col> 12 </el-row> 13 </el-container> 14 </div> 15 </template> 16 17 <script> 18 export default { 19 name:"Footer", 20 data(){ 21 return {} 22 } 23 } 24 </script> 25 26 27 <style scoped> 28 .footer{ 29 width: 100%; 30 height: 128px; 31 background: #25292e; 32 } 33 .footer .el-container{ 34 width: 1200px; 35 margin: auto; 36 } 37 .footer .el-row { 38 align-items: center; 39 padding: 0 200px; 40 padding-bottom: 15px; 41 width: 100%; 42 margin-top: 38px; 43 } 44 .footer .el-row a{ 45 color: #fff; 46 font-size: 14px; 47 } 48 .footer .el-row .copyright{ 49 text-align: center; 50 color: #fff; 51 font-size: 14px; 52 } 53 </style>
src/compoents
Course.vue
1 <template> 2 <div class="course"> 3 <Header/> 4 <div class="main"> 5 <!-- 筛选功能 --> 6 <div class="top"> 7 <ul class="condition condition1"> 8 <li class="cate-condition">课程分类:</li> 9 <li class="item" :class="query_params.course_category==0?'current':''" @click="query_params.course_category=0">全部</li> 10 <li :class="query_params.course_category==catetory.id?'current':''" @click="query_params.course_category=catetory.id" v-for="catetory in catetory_list" :data-key="catetory.id" class="item">{{catetory.name}}</li> 11 </ul> 12 <ul class="condition condition2"> 13 <li class="cate-condition">筛 选:</li> 14 <li class="item" :class="(query_params.ordering=='-id' || query_params.ordering=='id')?'current':''" @click="select_ordering('id')">默认</li> 15 <li class="item" :class="(query_params.ordering=='-students' || query_params.ordering=='students')?'current':''" @click="select_ordering('students')">人气</li> 16 <li class="item" :class="query_params.ordering=='price'?'current price':(query_params.ordering=='-price'?'current price2':'')" @click="select_ordering('price')">价格</li> 17 <li class="course-length">共21个课程</li> 18 </ul> 19 </div> 20 <!-- 课程列表 ---> 21 <div class="list"> 22 <ul> 23 <li class="course-item" v-for="course in course_list"> 24 <router-link :to="{path: '/detail',query:{id:course.id}}" class="course-link"> 25 <div class="course-cover"> 26 <img :src="course.course_img" alt=""> 27 </div> 28 <div class="course-info"> 29 <div class="course-title"> 30 <h3>{{course.name}}</h3> 31 <span>{{course.students}}人已加入学习</span> 32 </div> 33 <p class="teacher"> 34 <span class="info">{{course.teacher.name}} {{course.teacher.title}}</span> 35 <span class="lesson">共{{course.lessons}}课时/{{course.lessons==course.pub_lessons?'更新完成':('已更新'+course.pub_lessons+"课时")}}</span> 36 </p> 37 <ul class="lesson-list"> 38 <li v-for="lesson,key in course.lesson_list"> 39 <p class="lesson-title">0{{key+1}} | {{lesson.name}}</p> 40 <span v-if="lesson.free_trail" class="free">免费</span> 41 </li> 42 43 </ul> 44 <div class="buy-info"> 45 <div v-if="course.get_course_discount_type"> 46 <span class="discount">{{course.get_course_discount_type}}</span> 47 <span class="present-price">¥{{course.get_course_price}}元</span> 48 <span class="original-price">原价:{{course.price}}元</span> 49 </div> 50 <span v-else class="present-price">¥{{course.price}}元</span> 51 <button class="buy-now">立即购买</button> 52 </div> 53 </div> 54 </router-link> 55 </li> 56 </ul> 57 </div> 58 <div class="pagination"> 59 <el-pagination 60 @current-change="handleCurrentChange" 61 :current-page="query_params.current_page" 62 background 63 layout="prev, pager, next" 64 :page-size="course_page_size" 65 :total="course_count"> 66 </el-pagination> 67 </div> 68 </div> 69 <Footer/> 70 </div> 71 </template> 72 73 <script> 74 import Header from "./common/Header" 75 import Footer from "./common/Footer" 76 export default { 77 name: "Course", 78 data(){ 79 return { 80 catetory_list:[], 81 course_list:[], 82 course_count: 0, 83 course_page_size:1, 84 query_params:{ 85 course_category: 0, 86 ordering:"-id", 87 current_page: 1, 88 } 89 } 90 }, 91 watch:{ 92 // 每次点击不同课程时,要重新获取课程列表 93 "query_params.course_category":function(){ 94 this.get_course_list(); 95 // 当切换分类的时候,重置页码 96 this.query_params.current_page = 1; 97 }, 98 "query_params.ordering":function(){ 99 // 当切换排序条件的时候,重置页码 100 // this.query_params.current_page = 1; 101 this.get_course_list(); 102 }, 103 "query_params.current_page":function(){ 104 this.get_course_list(); 105 } 106 }, 107 components: {Header, Footer}, 108 created(){ 109 // 获取课程分类 110 this.$axios.get(this.$settings.Host+"/courses/cate/").then(response=>{ 111 this.catetory_list = response.data 112 }).catch(error=>{ 113 console.log(error.response) 114 }); 115 116 // 获取课程信息 117 this.get_course_list() 118 119 }, 120 methods:{ 121 select_ordering(selector){ 122 // 默认排序 123 if(this.query_params.ordering==('-'+selector) ){ 124 this.query_params.ordering = selector; 125 }else{ 126 this.query_params.ordering = '-'+selector; 127 } 128 }, 129 get_course_list(){ 130 let query_params = { 131 ordering:this.query_params.ordering, 132 page:this.query_params.current_page, 133 }; 134 135 if( this.query_params.course_category != 0 ){ 136 query_params.course_category = this.query_params.course_category; 137 } 138 139 this.$axios.get(this.$settings.Host+"/courses/list/",{ 140 params: query_params 141 }).then(response=>{ 142 // 课程列表 143 this.course_list = response.data.results; 144 // 课程总数量 145 this.course_count = response.data.count; 146 147 }).catch(error=>{ 148 console.log(error.response) 149 }); 150 }, 151 handleCurrentChange(page){ 152 // 页码发生改变 153 this.query_params.current_page = page; 154 } 155 } 156 } 157 </script> 158 159 <style scoped> 160 .main{ 161 width: 1100px; 162 height: auto; 163 margin: 0 auto; 164 padding-top: 35px; 165 } 166 .main .top{ 167 margin-bottom: 35px; 168 padding: 25px 30px 25px 20px; 169 background: #fff; 170 border-radius: 4px; 171 box-shadow: 0 2px 4px 0 #f0f0f0; 172 } 173 .condition{ 174 border-bottom: 1px solid #333; 175 border-bottom-color: rgba(51,51,51,.05); 176 padding-bottom: 18px; 177 margin-bottom: 17px; 178 overflow: hidden; 179 } 180 .condition li{ 181 float: left; 182 } 183 .condition .cate-condition{ 184 color: #888; 185 font-size: 16px; 186 } 187 .condition .item{ 188 padding: 6px 16px; 189 line-height: 16px; 190 margin-left: 14px; 191 position: relative; 192 transition: all .3s ease; 193 border:1px solid transparent; /* transparent 透明 */ 194 cursor: pointer; 195 color: #4a4a4a; 196 } 197 .condition1 .current{ 198 color: #ffc210; 199 border: 1px solid #ffc210!important; 200 border-radius: 30px; 201 } 202 .condition2 .current{ 203 color: #ffc210; 204 } 205 .condition .price:before{ 206 content: ""; 207 width: 0; 208 border: 5px solid transparent; 209 border-top-color: #d8d8d8; 210 position: absolute; 211 right: 0; 212 bottom: 2.5px; 213 } 214 .condition .price2:before{ 215 content: ""; 216 width: 0; 217 border: 5px solid transparent; 218 position: absolute; 219 right: 0; 220 bottom: 2.5px; 221 border-top-color: #ffc210; 222 } 223 .condition .price2:after{ 224 content: ""; 225 width: 0; 226 border: 5px solid transparent; 227 position: absolute; 228 right: 0; 229 top: 2.5px; 230 border-bottom-color: #d8d8d8; 231 } 232 .condition .price:after{ 233 content: ""; 234 width: 0; 235 border: 5px solid transparent; 236 border-bottom-color: #ffc210; 237 position: absolute; 238 right: 0; 239 top: 2.5px; 240 } 241 .condition2 .course-length{ 242 float: right; 243 font-size: 14px; 244 color: #9b9b9b; 245 } 246 .course-item{ 247 background: #fff; 248 padding: 20px 30px 20px 20px; 249 margin-bottom: 35px; 250 border-radius: 2px; 251 cursor: pointer; 252 box-shadow: 2px 3px 16px rgba(0,0,0,.1); 253 transition: all .2s ease; 254 overflow: hidden; 255 cursor:pointer; 256 } 257 .course-link{ 258 overflow: hidden; 259 } 260 .course-cover { 261 width: 423px; 262 height: 210px; 263 margin-right: 30px; 264 float: left; 265 } 266 .course-info{ 267 width: 597px; 268 float: left; 269 } 270 .course-title{ 271 margin-bottom: 8px; 272 overflow: hidden; 273 274 } 275 .course-title h3{ 276 font-size: 26px; 277 color: #333; 278 float: left; 279 } 280 .course-title span { 281 float: right; 282 font-size: 14px; 283 color: #9b9b9b; 284 margin-top: 12px; 285 text-indent: 1em; /* 缩进 2字符宽度 */ 286 background: url("../assets/people.svg") no-repeat 0px 3px; 287 } 288 .teacher{ 289 justify-content: space-between; 290 font-size: 14px; 291 color: #9b9b9b; 292 margin-bottom: 14px; 293 padding-bottom: 14px; 294 border-bottom: 1px solid #333; 295 border-bottom-color: rgba(51,51,51,.05); 296 } 297 .teacher .lesson{ 298 float: right; 299 } 300 .lesson-list{ 301 overflow: hidden; 302 } 303 .lesson-list li{ 304 width: 49%; 305 margin-bottom: 15px; 306 cursor: pointer; 307 float: left; 308 margin-right:1%; 309 } 310 .lesson-list li .player{ 311 width: 16px; 312 height: 16px; 313 vertical-align: text-bottom; 314 } 315 .lesson-list li .lesson-title { 316 display: inline-block; 317 max-width: 227px; 318 text-overflow: ellipsis; /* 如果字体太多超出元素的宽度,则添加省略符号 */ 319 color: #666; 320 overflow: hidden; 321 white-space: nowrap; 322 font-size: 14px; 323 vertical-align: text-bottom; /* 文本的垂直对齐方式: text-botton 文本底部对齐 */ 324 text-indent: 1.5em; 325 background: url(../../static/player.svg) no-repeat 0px 3px; 326 } 327 328 .lesson-list .free{ 329 width: 34px; 330 height: 20px; 331 color: #fd7b4d; 332 margin-left: 10px; 333 border: 1px solid #fd7b4d; 334 border-radius: 2px; 335 text-align: center; 336 font-size: 13px; 337 white-space: nowrap; 338 } 339 .lesson-list li:hover .lesson-title{ 340 color: #ffc210; 341 background-image: url(../../static/player2.svg); 342 } 343 .lesson-list li:hover .free{ 344 border-color: #ffc210; 345 color: #ffc210; 346 } 347 348 .buy-info .discount{ 349 padding: 0px 10px; 350 font-size: 16px; 351 color: #fff; 352 display: inline-block; 353 height: 36px; 354 text-align: center; 355 margin-right: 8px; 356 background: #fa6240; 357 border: 1px solid #fa6240; 358 border-radius: 10px 0 10px 0; 359 line-height: 36px; 360 } 361 .present-price{ 362 font-size: 24px; 363 color: #fa6240; 364 } 365 .original-price{ 366 text-decoration: line-through; 367 font-size: 14px; 368 color: #9b9b9b; 369 margin-left: 10px; 370 } 371 .buy-now{ 372 width: 120px; 373 height: 38px; 374 background: transparent; 375 color: #fa6240; 376 font-size: 16px; 377 border: 1px solid #fd7b4d; 378 border-radius: 3px; 379 transition: all .2s ease-in-out; /* 过渡动画 */ 380 float: right; 381 margin-top: 5px; 382 } 383 .buy-now:hover{ 384 color: #fff; 385 background: #ffc210; 386 border: 1px solid #ffc210; 387 cursor: pointer; 388 } 389 .pagination{ 390 text-align: center; 391 margin: 20px 0px 50px 0px; 392 } 393 </style>
User.vue
1 <template> 2 <div class="user-order"> 3 <Header/> 4 <div class="main"> 5 <div class="banner"></div> 6 <div class="profile"> 7 <div class="profile-info"> 8 <div class="avatar"><img class="newImg" width="100%" alt="" src="/static/img/logo@2x.png"></div> 9 <span class="user-name">Mixtea</span> 10 <span class="user-job">深圳市 | 程序员</span> 11 </div> 12 <ul class="my-item"> 13 <li class="active">我的账户</li> 14 <li>我的订单</li> 15 <li>个人资料</li> 16 <li>账号安全</li> 17 </ul> 18 </div> 19 <div class="user-data"> 20 21 </div> 22 </div> 23 <Footer/> 24 </div> 25 </template> 26 27 <script> 28 import Header from "./common/Header" 29 import Footer from "./common/Footer" 30 export default{ 31 name:"UserOrder", 32 data(){ 33 return { 34 }; 35 }, 36 components:{ 37 Header, 38 Footer, 39 } 40 } 41 </script> 42 43 <style scoped> 44 .user-order{ 45 padding-top: 80px; 46 } 47 .main .banner{ 48 width: 100%; 49 height: 324px; 50 background: url(/static/img/my_bkging.0648ebe.png) no-repeat; 51 background-size: cover; 52 z-index: 1; 53 } 54 .profile{ 55 width: 1200px; 56 margin: 0 auto; 57 } 58 .profile-info{ 59 text-align: center; 60 margin-top: -80px; 61 } 62 .avatar{ 63 width: 120px; 64 height: 120px; 65 border-radius: 60px; 66 overflow: hidden; 67 margin: 0 auto; 68 } 69 .user-name{ 70 display: block; 71 font-size: 24px; 72 color: #4a4a4a; 73 margin-top: 14px; 74 } 75 .user-job{ 76 display: block; 77 font-size: 11px; 78 color: #9b9b9b; 79 } 80 .my-item{ 81 list-style: none; 82 line-height: 1.42857143; 83 color: #333; 84 width: 474px; 85 height: 31px; 86 display: -ms-flexbox; 87 display: flex; 88 cursor: pointer; 89 margin: 41px auto 0; 90 -ms-flex-pack: justify; 91 justify-content: space-between; 92 } 93 .my-item .active{ 94 border-bottom: 1px solid #000; 95 } 96 .user-data{ 97 width: 1200px; 98 height: auto; 99 margin: 0 auto; 100 padding-top: 30px; 101 border-top: 1px solid #e8e8e8; 102 margin-bottom: 63px; 103 } 104 .nav{ 105 width: 100%; 106 height: 60px; 107 background: #e9e9e9; 108 display: -ms-flexbox; 109 display: flex; 110 -ms-flex-align: center; 111 align-items: center; 112 } 113 .nav li{ 114 margin-left: 20px; 115 margin-right: 28px; 116 height: 60px; 117 line-height: 60px; 118 list-style: none; 119 font-family: PingFangSC-Medium; 120 font-size: 13px; 121 color: #333; 122 border-bottom: 1px solid #e9e9e9; 123 width: 160px; 124 } 125 .nav .order-info{ width: 325px; } 126 .nav .course-expire{ width: 60px; } 127 .nav .course-price{ width: 130px; } 128 .user-data-header{ 129 display: flex; 130 height: 44px; 131 color: #4a4a4a; 132 font-size: 14px; 133 background: #f3f3f3; 134 -ms-flex-align: center; 135 align-items: center; 136 font-family: PingFangSC-Regular; 137 } 138 .order-time{ 139 font-size: 12px; 140 display: inline-block; 141 margin-left: 20px; 142 } 143 .order-num{ 144 font-size: 12px; 145 display: inline-block; 146 margin-left: 29px; 147 } 148 .user-data-list{ 149 height: 100%; 150 display: flex; 151 } 152 .user-data-list{ 153 background: none; 154 } 155 .user-data-list li{ 156 height: 60px; 157 line-height: 60px; 158 } 159 .user-data-list .order-info{ 160 display: flex; 161 align-items: center; 162 margin-right: 28px; 163 } 164 .user-data-list .order-info img{ 165 max-width: 100px; 166 max-height: 75px; 167 margin-right: 22px; 168 } 169 .course-title{ 170 width: 203px; 171 font-size: 13px; 172 color: #333; 173 line-height: 20px; 174 font-family: PingFangSC-Medium; 175 margin-top: -10px; 176 } 177 .order-info-title .price-service{ 178 line-height: 18px; 179 } 180 .price-service{ 181 font-size: 12px; 182 color: #fa6240; 183 padding: 0 5px; 184 border: 1px solid #fa6240; 185 border-radius: 4px; 186 margin-top: 4px; 187 position: absolute; 188 } 189 .order-info-title{ 190 margin-top: -10px; 191 } 192 .user-data-list .course-expire{ 193 font-size: 12px; 194 color: #ff5502; 195 font-family: PingFangSC-Medium; 196 width: 60px; 197 198 text-align: center; 199 } 200 .btn { 201 width: 100px; 202 height: 32px; 203 font-size: 14px; 204 color: #fff; 205 background: #ffc210; 206 border-radius: 4px; 207 border: none; 208 outline: none; 209 font-family: PingFangSC-Medium; 210 transition: all .25s ease; 211 display: inline-block; 212 line-height: 32px; 213 text-align: center; 214 cursor: pointer; 215 } 216 </style>
Success.vue
1 <template> 2 <div class="success"> 3 <Header/> 4 <div class="main"> 5 <div class="title"> 6 <!-- <img src="../../static/images/right.svg" alt="">--> 7 <div class="success-tips"> 8 <p class="tips1">您已成功购买 {{order_info.course_list.length}} 门课程!</p> 9 <p class="tips2">你还可以加入QQ群 <span>747556033</span> 学习交流</p> 10 </div> 11 </div> 12 <div class="order-info"> 13 <p class="info1"><b>付款时间:</b><span>{{order_info.pay_time}}</span></p> 14 <p class="info2"><b>付款金额:</b><span >¥{{order_info.real_price}}元</span></p> 15 <p class="info3"><b>课程信息:</b><span><span>{{order_info.course_list2}}</span></span></p> 16 </div> 17 <div class="wechat-code"> 18 <!-- <img src="../../static/images/server.cf99f78.png" alt="" class="er">--> 19 <!-- <p><img src="../../static/images/tan.svg" alt="">重要!微信扫码关注获得学习通知&课程更新提醒!否则将严重影响学习进度和课程体验!</p>--> 20 </div> 21 <div class="study"> 22 <span>立即学习</span> 23 </div> 24 </div> 25 <Footer/> 26 </div> 27 </template> 28 29 <script> 30 import Header from "./common/Header" 31 import Footer from "./common/Footer" 32 export default{ 33 name:"Success", 34 data(){ 35 return { 36 order_info:{ 37 course_list:[] 38 } 39 }; 40 }, 41 created(){ 42 let token = localStorage.token || sessionStorage.token; 43 44 if(!token){ 45 this.$alert("对不起,您尚未登录!请登录!","警告",{ 46 callback(){ 47 _this.$router.push("/login"); 48 } 49 }) 50 } 51 52 // 转发支付结果到后端 53 this.$axios.get(this.$settings.Host+"/payments/alipay/result/"+location.search,{ 54 headers:{ 55 // 注意下方的空格!!! 56 "Authorization":"jwt " + token, 57 }, 58 }).then(response=>{ 59 this.order_info = response.data.message; 60 this.order_info.course_list2 = ""; 61 this.order_info.course_list.forEach(course=>{ 62 this.order_info.course_list2+=`《${course}》`; 63 }) 64 }).catch(error=>{ 65 66 console.log(error.response); 67 }) 68 }, 69 components:{ 70 Header, 71 Footer, 72 } 73 } 74 </script> 75 76 <style scoped> 77 .success{ 78 padding-top: 80px; 79 } 80 .main{ 81 height: 100%; 82 padding-top: 25px; 83 padding-bottom: 25px; 84 margin: 0 auto; 85 width: 1200px; 86 background: #fff; 87 } 88 .main .title{ 89 display: flex; 90 -ms-flex-align: center; 91 align-items: center; 92 padding: 25px 40px; 93 border-bottom: 1px solid #f2f2f2; 94 } 95 .main .title .success-tips{ 96 box-sizing: border-box; 97 } 98 .title img{ 99 vertical-align: middle; 100 width: 60px; 101 height: 60px; 102 margin-right: 40px; 103 } 104 .title .success-tips{ 105 box-sizing: border-box; 106 } 107 .title .tips1{ 108 font-size: 22px; 109 color: #000; 110 } 111 .title .tips2{ 112 font-size: 16px; 113 color: #4a4a4a; 114 letter-spacing: 0; 115 text-align: center; 116 margin-top: 10px; 117 } 118 .title .tips2 span{ 119 color: #ec6730; 120 } 121 .order-info{ 122 padding: 25px 48px; 123 padding-bottom: 15px; 124 border-bottom: 1px solid #f2f2f2; 125 } 126 .order-info p{ 127 font-family: PingFangSC-Regular; 128 display: -ms-flexbox; 129 display: flex; 130 margin-bottom: 10px; 131 font-size: 16px; 132 } 133 .order-info p b{ 134 font-weight: 400; 135 color: #9d9d9d; 136 white-space: nowrap; 137 } 138 .wechat-code{ 139 display: flex; 140 -ms-flex-align: center; 141 align-items: center; 142 padding: 25px 40px; 143 border-bottom: 1px solid #f2f2f2; 144 } 145 .wechat-code>img{ 146 width: 100px; 147 height: 100px; 148 margin-right: 15px; 149 } 150 .wechat-code p{ 151 font-family: PingFangSC-Regular; 152 font-size: 14px; 153 color: #d0021b; 154 display: -ms-flexbox; 155 display: flex; 156 -ms-flex-align: center; 157 align-items: center; 158 } 159 .wechat-code p>img{ 160 width: 16px; 161 height: 16px; 162 margin-right: 10px; 163 } 164 .study{ 165 padding: 25px 40px; 166 } 167 .study span{ 168 display: block; 169 width: 140px; 170 height: 42px; 171 text-align: center; 172 line-height: 42px; 173 cursor: pointer; 174 background: #ffc210; 175 border-radius: 6px; 176 font-family: PingFangSC-Regular; 177 font-size: 16px; 178 color: #fff; 179 } 180 </style>
Register.vue
1 <template> 2 <div class="box"> 3 <img src="../../static/img/Loginbg.3377d0c.jpg" alt=""> 4 <div class="register"> 5 <div class="register_box"> 6 <div class="register-title">注册路飞学城</div> 7 <div class="inp"> 8 <input v-model = "mobile" type="text" placeholder="手机号码" class="user"> 9 <input v-model = "password" type="password" placeholder="登录密码" class="user"> 10 <input v-model = "password2" type="password" placeholder="确认密码" class="user"> 11 <div id="geetest"></div> 12 <div class="sms-box"> 13 <input v-model = "sms" type="text" placeholder="输入验证码" class="user"> 14 <div class="sms-btn" @click="smsHandle">{{sms_text}}</div> 15 </div> 16 <button class="register_btn" @click="registerHander">注册</button> 17 <p class="go_login" >已有账号 <router-link to="/login">直接登录</router-link></p> 18 </div> 19 </div> 20 </div> 21 </div> 22 </template> 23 24 <script> 25 export default { 26 name: 'Register', 27 data(){ 28 return { 29 sms:"", 30 mobile:"", 31 password:"", 32 password2:"", 33 validateResult:false, 34 is_send: false, // 是否已经发送短信的状态 35 send_intervel:60, // 发送短信的间隔 36 sms_text:"点击发送短信", // 发送短信的提示 37 } 38 }, 39 methods:{ 40 // 发送短信 41 smsHandle() { 42 // 判断是否填写了手机 43 if( !/^\d{11}$/.test(this.mobile) ){ 44 this.$alert('手机号码格式有误!', '警告'); 45 return false; 46 } 47 48 // 判断是否在60s内有发送过短信,如果有则,不能点击发送 49 if(this.is_send){ 50 this.$alert('60s内不能频繁发送短信!', '警告'); 51 return false; 52 } 53 54 55 let _this = this; 56 57 _this.$axios.get(_this.$settings.Host+`/users/sms/${_this.mobile}/`).then(response=>{ 58 let data = response.data; 59 if( data.result == '-1' ){ 60 _this.$alert("发送短信失败!","错误"); 61 }else{ 62 _this.is_send = true; 63 _this.$alert("发送短信成功了!","成功",{ 64 callback(){ 65 let num = _this.send_intervel 66 let timer = setInterval(()=>{ 67 if(num<1){ 68 clearInterval(timer); 69 _this.sms_text = "点击发送短信"; 70 _this.is_send = false; 71 }else{ 72 num--; 73 _this.sms_text = num+"后可继续点击发送"; 74 } 75 },1000) 76 } 77 }); 78 } 79 }).catch(error=>{ 80 console.log(error.response) 81 }) 82 83 }, 84 // 提交注册信息 85 registerHander(){ 86 // 验证手机号码 87 if( !/^\d{11}$/.test(this.mobile) ){ 88 this.$alert('手机号码格式有误!', '警告'); 89 return false; 90 } 91 92 // 密码长度 93 if( !/^.{6,16}$/.test(this.password) ){ 94 this.$alert('密码长度必须在6-16位字符之间!', '警告'); 95 return false; 96 } 97 98 // 密码和确认密码 99 if( this.password != this.password2 ){ 100 this.$alert('确认密码必须和密码保持一致!', '警告'); 101 return false; 102 } 103 104 // 发送请求注册用户 105 this.$axios.post(this.$settings.Host+"/users/register/",{ 106 mobile:this.mobile, 107 password:this.password, 108 password2:this.password2, 109 sms_code:this.sms, 110 }).then(response=>{ 111 let _this = this; 112 _this.$alert("注册成功!","路飞学成",{ 113 callback(){ 114 let data = response.data; 115 console.log(data); 116 // 保存登录状态 117 sessionStorage.token = data.token; 118 sessionStorage.user_id = data.id; 119 sessionStorage.user_name = data.username; 120 121 // 跳转到首页 122 _this.$router.push("/"); 123 } 124 }); 125 126 }).catch(error=>{ 127 console.log( error.response ) 128 }) 129 130 } 131 }, 132 133 }; 134 </script> 135 136 <style scoped> 137 .box{ 138 width: 100%; 139 height: 100%; 140 position: relative; 141 overflow: hidden; 142 margin-top: -80px; 143 } 144 .box img{ 145 width: 100%; 146 min-height: 100%; 147 } 148 .box .register { 149 position: absolute; 150 width: 500px; 151 height: 400px; 152 top: 0; 153 left: 0; 154 margin: auto; 155 right: 0; 156 bottom: 0; 157 top: -220px; 158 } 159 .register .register-title{ 160 width: 100%; 161 font-size: 24px; 162 text-align: center; 163 padding-top: 30px; 164 padding-bottom: 30px; 165 color: #4a4a4a; 166 letter-spacing: .39px; 167 } 168 .register-title img{ 169 width: 190px; 170 height: auto; 171 } 172 .register-title p{ 173 font-family: PingFangSC-Regular; 174 font-size: 18px; 175 color: #fff; 176 letter-spacing: .29px; 177 padding-top: 10px; 178 padding-bottom: 50px; 179 } 180 .register_box{ 181 width: 400px; 182 height: auto; 183 background: #fff; 184 box-shadow: 0 2px 4px 0 rgba(0,0,0,.5); 185 border-radius: 4px; 186 margin: 0 auto; 187 padding-bottom: 40px; 188 } 189 .register_box .title{ 190 font-size: 20px; 191 color: #9b9b9b; 192 letter-spacing: .32px; 193 border-bottom: 1px solid #e6e6e6; 194 display: flex; 195 justify-content: space-around; 196 padding: 50px 60px 0 60px; 197 margin-bottom: 20px; 198 cursor: pointer; 199 } 200 .register_box .title span:nth-of-type(1){ 201 color: #4a4a4a; 202 border-bottom: 2px solid #84cc39; 203 } 204 205 .inp{ 206 width: 350px; 207 margin: 0 auto; 208 } 209 .inp input{ 210 border: 0; 211 outline: 0; 212 width: 100%; 213 height: 45px; 214 border-radius: 4px; 215 border: 1px solid #d9d9d9; 216 text-indent: 20px; 217 font-size: 14px; 218 background: #fff !important; 219 } 220 .inp input.user{ 221 margin-bottom: 16px; 222 } 223 .inp .rember{ 224 display: flex; 225 justify-content: space-between; 226 align-items: center; 227 position: relative; 228 margin-top: 10px; 229 } 230 .inp .rember p:first-of-type{ 231 font-size: 12px; 232 color: #4a4a4a; 233 letter-spacing: .19px; 234 margin-left: 22px; 235 display: -ms-flexbox; 236 display: flex; 237 -ms-flex-align: center; 238 align-items: center; 239 /*position: relative;*/ 240 } 241 .inp .rember p:nth-of-type(2){ 242 font-size: 14px; 243 color: #9b9b9b; 244 letter-spacing: .19px; 245 cursor: pointer; 246 } 247 248 .inp .rember input{ 249 outline: 0; 250 width: 30px; 251 height: 45px; 252 border-radius: 4px; 253 border: 1px solid #d9d9d9; 254 text-indent: 20px; 255 font-size: 14px; 256 background: #fff !important; 257 } 258 259 .inp .rember p span{ 260 display: inline-block; 261 font-size: 12px; 262 width: 100px; 263 /*position: absolute;*/ 264 /*left: 20px;*/ 265 266 } 267 #geetest{ 268 margin-top: 20px; 269 } 270 .register_btn{ 271 width: 100%; 272 height: 45px; 273 background: #84cc39; 274 border-radius: 5px; 275 font-size: 16px; 276 color: #fff; 277 letter-spacing: .26px; 278 margin-top: 30px; 279 } 280 .inp .go_login{ 281 text-align: center; 282 font-size: 14px; 283 color: #9b9b9b; 284 letter-spacing: .26px; 285 padding-top: 20px; 286 } 287 .inp .go_login span{ 288 color: #84cc39; 289 cursor: pointer; 290 } 291 .sms-box{ 292 position: relative; 293 } 294 .sms-btn{ 295 font-size: 14px; 296 color: #ffc210; 297 letter-spacing: .26px; 298 position: absolute; 299 right: 16px; 300 top: 10px; 301 cursor: pointer; 302 overflow: hidden; 303 background: #fff; 304 border-left: 1px solid #484848; 305 padding-left: 16px; 306 padding-bottom: 4px; 307 } 308 </style>
Login.vue
1 <template> 2 <div class="login-box"> 3 <img src="../../static/img/Loginbg.3377d0c.jpg" alt=""> 4 <div class="login"> 5 <div class="login-title"> 6 <img src="../../static/img/Logotitle.1ba5466.png" alt=""> 7 <p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p> 8 </div> 9 <div class="login_box"> 10 <div class="title"> 11 <span @click="login_type=0">密码登录</span> 12 <span @click="login_type=1">短信登录</span> 13 </div> 14 <div class="inp" v-if="login_type==0"> 15 <input v-model = "username" type="text" placeholder="用户名 / 手机号码" class="user"> 16 <input v-model = "password" type="password" name="" class="pwd" placeholder="密码"> 17 <div id="geetest1"></div> 18 <div class="rember"> 19 <p> 20 <input type="checkbox" class="no" v-model="remember"/> 21 <span>记住密码</span> 22 </p> 23 <p>忘记密码</p> 24 </div> 25 <button class="login_btn" @click="loginhander">登录</button> 26 <p class="go_login" >没有账号 <router-link to="/reg">立即注册</router-link></p> 27 </div> 28 <div class="inp" v-show="login_type==1"> 29 <input v-model = "username" type="text" placeholder="手机号码" class="user"> 30 <input v-model = "password" type="text" class="pwd" placeholder="短信验证码"> 31 <button id="get_code">获取验证码</button> 32 <button class="login_btn">登录</button> 33 <p class="go_login" >没有账号 <router-link to="/reg">立即注册</router-link></p> 34 </div> 35 </div> 36 </div> 37 </div> 38 </template> 39 40 <script> 41 export default { 42 name: 'Login', 43 data(){ 44 return { 45 login_type: 0, 46 username:"", 47 password:"", 48 remember:"", 49 is_geek:false, 50 } 51 }, 52 mounted(){ 53 // 请求后端获取生成验证码的流水号 54 this.$axios.get(this.$settings.Host + "/users/captcha/",{ 55 responseType: 'json', // 希望返回json数据 56 }).then(response => { 57 let data = response.data; 58 59 // 验证初始化配置 60 initGeetest({ 61 gt: data.gt, 62 challenge: data.challenge, 63 product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效 64 offline: !data.success 65 },this.handlerPopup) 66 }).catch(error => { 67 console.log(error.response); 68 }); 69 }, 70 methods:{ 71 // 用户登录 72 loginhander(){ 73 // 判断用户是否已经通过了极验验证 74 if(!this.is_geek){ 75 return false; 76 } 77 78 this.$axios.post(this.$settings.Host+"/users/login/",{ 79 username:this.username, 80 password:this.password, 81 }).then(response=>{ 82 let data = response.data 83 // 根据用户是否勾选了记住密码来保存用户认证信息 84 if(this.remember){ 85 // 记住密码 86 localStorage.token = data.token; 87 localStorage.user_id = data.id; 88 localStorage.user_name = data.username; 89 90 }else{ 91 // 不需要记住密码 92 sessionStorage.token = data.token; 93 sessionStorage.user_id = data.id; 94 sessionStorage.user_name = data.username; 95 } 96 97 // 登录成功以后,跳转会上一个页面 98 this.$router.go(-1); 99 100 }).catch(error=>{ 101 console.log(error.response) 102 }) 103 }, 104 // 验证码的成功验证事件方法 105 handlerPopup(captchaObj){ 106 // 把验证码添加到模板中制定的页面 107 captchaObj.appendTo("#geetest1"); 108 109 // 记录vue对象 110 let _this = this; 111 112 // 监听用户对于验证码的操作是否成功了 113 captchaObj.onSuccess(()=>{ 114 var validate = captchaObj.getValidate(); 115 116 _this.$axios.post(_this.$settings.Host+"/users/captcha/",{ 117 geetest_challenge: validate.geetest_challenge, 118 geetest_validate: validate.geetest_validate, 119 geetest_seccode: validate.geetest_seccode 120 }).then(response=>{ 121 // 在用户成功添加数据以后,可以允许点击登录按钮 122 _this.is_geek = true; 123 124 }).catch(error=>{ 125 console.log(error.response) 126 }) 127 128 }); 129 130 }, 131 }, 132 133 }; 134 </script> 135 <style scoped> 136 .login-box{ 137 width: 100%; 138 height: 100%; 139 position: relative; 140 overflow: hidden; 141 margin-top: -80px; 142 } 143 .login-box img{ 144 width: 100%; 145 min-height: 100%; 146 } 147 .login-box .login { 148 position: absolute; 149 width: 500px; 150 height: 400px; 151 left: 0; 152 margin: auto; 153 right: 0; 154 bottom: 0; 155 top: -220px; 156 } 157 .login .login-title{ 158 width: 100%; 159 text-align: center; 160 } 161 .login-title img{ 162 width: 190px; 163 height: auto; 164 } 165 .login-title p{ 166 font-size: 18px; 167 color: #fff; 168 letter-spacing: .29px; 169 padding-top: 10px; 170 padding-bottom: 50px; 171 } 172 .login_box{ 173 width: 400px; 174 height: auto; 175 background: #fff; 176 box-shadow: 0 2px 4px 0 rgba(0,0,0,.5); 177 border-radius: 4px; 178 margin: 0 auto; 179 padding-bottom: 40px; 180 } 181 .login_box .title{ 182 font-size: 20px; 183 color: #9b9b9b; 184 letter-spacing: .32px; 185 border-bottom: 1px solid #e6e6e6; 186 display: flex; 187 justify-content: space-around; 188 padding: 50px 60px 0 60px; 189 margin-bottom: 20px; 190 cursor: pointer; 191 } 192 .login_box .title span:nth-of-type(1){ 193 color: #4a4a4a; 194 border-bottom: 2px solid #84cc39; 195 } 196 197 .inp{ 198 width: 350px; 199 margin: 0 auto; 200 } 201 .inp input{ 202 outline: 0; 203 width: 100%; 204 height: 45px; 205 border-radius: 4px; 206 border: 1px solid #d9d9d9; 207 text-indent: 20px; 208 font-size: 14px; 209 background: #fff !important; 210 } 211 .inp input.user{ 212 margin-bottom: 16px; 213 } 214 .inp .rember{ 215 display: flex; 216 justify-content: space-between; 217 align-items: center; 218 position: relative; 219 margin-top: 10px; 220 } 221 .inp .rember p:first-of-type{ 222 font-size: 12px; 223 color: #4a4a4a; 224 letter-spacing: .19px; 225 margin-left: 22px; 226 display: -ms-flexbox; 227 display: flex; 228 -ms-flex-align: center; 229 align-items: center; 230 /*position: relative;*/ 231 } 232 .inp .rember p:nth-of-type(2){ 233 font-size: 14px; 234 color: #9b9b9b; 235 letter-spacing: .19px; 236 cursor: pointer; 237 } 238 239 .inp .rember input{ 240 outline: 0; 241 width: 30px; 242 height: 45px; 243 border-radius: 4px; 244 border: 1px solid #d9d9d9; 245 text-indent: 20px; 246 font-size: 14px; 247 background: #fff !important; 248 } 249 250 .inp .rember p span{ 251 display: inline-block; 252 font-size: 12px; 253 width: 100px; 254 /*position: absolute;*/ 255 /*left: 20px;*/ 256 257 } 258 #geetest{ 259 margin-top: 20px; 260 } 261 .login_btn{ 262 width: 100%; 263 height: 45px; 264 background: #84cc39; 265 border-radius: 5px; 266 font-size: 16px; 267 color: #fff; 268 letter-spacing: .26px; 269 margin-top: 30px; 270 } 271 .inp .go_login{ 272 text-align: center; 273 font-size: 14px; 274 color: #9b9b9b; 275 letter-spacing: .26px; 276 padding-top: 20px; 277 } 278 .inp .go_login span{ 279 color: #84cc39; 280 cursor: pointer; 281 } 282 </style>
Home.vue
1 <template> 2 <div id="home"> 3 <Header/> 4 <Banner/> 5 <Footer/> 6 </div> 7 </template> 8 9 <script> 10 import Header from "./common/Header" 11 import Banner from "./common/Banner" 12 import Footer from "./common/Footer" 13 14 export default { 15 name:"Home", 16 data(){ 17 return { 18 19 } 20 }, 21 components:{ 22 Header, 23 Banner, 24 Footer, 25 } 26 } 27 </script> 28 29 <style scoped> 30 31 </style>
src/components/..
Regist.vue
1 <template> 2 <div class="box"> 3 <img src="../../static/img/Loginbg.3377d0c.jpg" alt=""> 4 <div class="register"> 5 <div class="register_box"> 6 <div class="register-title">注册路飞学城</div> 7 <div class="inp"> 8 <input v-model = "mobile" type="text" placeholder="手机号码" class="user"> 9 <input v-model = "password" type="password" placeholder="登录密码" class="user"> 10 <input v-model = "password2" type="password" placeholder="确认密码" class="user"> 11 <div id="geetest"></div> 12 <div class="sms-box"> 13 <input v-model = "sms" type="text" placeholder="输入验证码" class="user"> 14 <div class="sms-btn" @click="smsHandle">{{sms_text}}</div> 15 </div> 16 <button class="register_btn" @click="registerHander">注册</button> 17 <p class="go_login" >已有账号 <router-link to="/login">直接登录</router-link></p> 18 </div> 19 </div> 20 </div> 21 </div> 22 </template> 23 24 <script> 25 export default { 26 name: 'Register', 27 data(){ 28 return { 29 sms:"", 30 mobile:"", 31 password:"", 32 password2:"", 33 validateResult:false, 34 is_send: false, // 是否已经发送短信的状态 35 send_intervel:60, // 发送短信的间隔 36 sms_text:"点击发送短信", // 发送短信的提示 37 } 38 }, 39 methods:{ 40 // 发送短信 41 smsHandle() { 42 // 判断是否填写了手机 43 if( !/^\d{11}$/.test(this.mobile) ){ 44 this.$alert('手机号码格式有误!', '警告'); 45 return false; 46 } 47 48 // 判断是否在60s内有发送过短信,如果有则,不能点击发送 49 if(this.is_send){ 50 this.$alert('60s内不能频繁发送短信!', '警告'); 51 return false; 52 } 53 54 55 let _this = this; 56 57 _this.$axios.get(_this.$settings.Host+`/users/sms/${_this.mobile}/`).then(response=>{ 58 let data = response.data; 59 if( data.result == '-1' ){ 60 _this.$alert("发送短信失败!","错误"); 61 }else{ 62 _this.is_send = true; 63 _this.$alert("发送短信成功了!","成功",{ 64 callback(){ 65 let num = _this.send_intervel 66 let timer = setInterval(()=>{ 67 if(num<1){ 68 clearInterval(timer); 69 _this.sms_text = "点击发送短信"; 70 _this.is_send = false; 71 }else{ 72 num--; 73 _this.sms_text = num+"后可继续点击发送"; 74 } 75 },1000) 76 } 77 }); 78 } 79 }).catch(error=>{ 80 console.log(error.response) 81 }) 82 83 }, 84 // 提交注册信息 85 registerHander(){ 86 // 验证手机号码 87 if( !/^\d{11}$/.test(this.mobile) ){ 88 this.$alert('手机号码格式有误!', '警告'); 89 return false; 90 } 91 92 // 密码长度 93 if( !/^.{6,16}$/.test(this.password) ){ 94 this.$alert('密码长度必须在6-16位字符之间!', '警告'); 95 return false; 96 } 97 98 // 密码和确认密码 99 if( this.password != this.password2 ){ 100 this.$alert('确认密码必须和密码保持一致!', '警告'); 101 return false; 102 } 103 104 // 发送请求注册用户 105 this.$axios.post(this.$settings.Host+"/users/register/",{ 106 mobile:this.mobile, 107 password:this.password, 108 password2:this.password2, 109 sms_code:this.sms, 110 }).then(response=>{ 111 let _this = this; 112 _this.$alert("注册成功!","路飞学成",{ 113 callback(){ 114 let data = response.data; 115 console.log(data); 116 // 保存登录状态 117 sessionStorage.token = data.token; 118 sessionStorage.user_id = data.id; 119 sessionStorage.user_name = data.username; 120 121 // 跳转到首页 122 _this.$router.push("/"); 123 } 124 }); 125 126 }).catch(error=>{ 127 console.log( error.response ) 128 }) 129 130 } 131 }, 132 133 }; 134 </script> 135 136 <style scoped> 137 .box{ 138 width: 100%; 139 height: 100%; 140 position: relative; 141 overflow: hidden; 142 margin-top: -80px; 143 } 144 .box img{ 145 width: 100%; 146 min-height: 100%; 147 } 148 .box .register { 149 position: absolute; 150 width: 500px; 151 height: 400px; 152 top: 0; 153 left: 0; 154 margin: auto; 155 right: 0; 156 bottom: 0; 157 top: -220px; 158 } 159 .register .register-title{ 160 width: 100%; 161 font-size: 24px; 162 text-align: center; 163 padding-top: 30px; 164 padding-bottom: 30px; 165 color: #4a4a4a; 166 letter-spacing: .39px; 167 } 168 .register-title img{ 169 width: 190px; 170 height: auto; 171 } 172 .register-title p{ 173 font-family: PingFangSC-Regular; 174 font-size: 18px; 175 color: #fff; 176 letter-spacing: .29px; 177 padding-top: 10px; 178 padding-bottom: 50px; 179 } 180 .register_box{ 181 width: 400px; 182 height: auto; 183 background: #fff; 184 box-shadow: 0 2px 4px 0 rgba(0,0,0,.5); 185 border-radius: 4px; 186 margin: 0 auto; 187 padding-bottom: 40px; 188 } 189 .register_box .title{ 190 font-size: 20px; 191 color: #9b9b9b; 192 letter-spacing: .32px; 193 border-bottom: 1px solid #e6e6e6; 194 display: flex; 195 justify-content: space-around; 196 padding: 50px 60px 0 60px; 197 margin-bottom: 20px; 198 cursor: pointer; 199 } 200 .register_box .title span:nth-of-type(1){ 201 color: #4a4a4a; 202 border-bottom: 2px solid #84cc39; 203 } 204 205 .inp{ 206 width: 350px; 207 margin: 0 auto; 208 } 209 .inp input{ 210 border: 0; 211 outline: 0; 212 width: 100%; 213 height: 45px; 214 border-radius: 4px; 215 border: 1px solid #d9d9d9; 216 text-indent: 20px; 217 font-size: 14px; 218 background: #fff !important; 219 } 220 .inp input.user{ 221 margin-bottom: 16px; 222 } 223 .inp .rember{ 224 display: flex; 225 justify-content: space-between; 226 align-items: center; 227 position: relative; 228 margin-top: 10px; 229 } 230 .inp .rember p:first-of-type{ 231 font-size: 12px; 232 color: #4a4a4a; 233 letter-spacing: .19px; 234 margin-left: 22px; 235 display: -ms-flexbox; 236 display: flex; 237 -ms-flex-align: center; 238 align-items: center; 239 /*position: relative;*/ 240 } 241 .inp .rember p:nth-of-type(2){ 242 font-size: 14px; 243 color: #9b9b9b; 244 letter-spacing: .19px; 245 cursor: pointer; 246 } 247 248 .inp .rember input{ 249 outline: 0; 250 width: 30px; 251 height: 45px; 252 border-radius: 4px; 253 border: 1px solid #d9d9d9; 254 text-indent: 20px; 255 font-size: 14px; 256 background: #fff !important; 257 } 258 259 .inp .rember p span{ 260 display: inline-block; 261 font-size: 12px; 262 width: 100px; 263 /*position: absolute;*/ 264 /*left: 20px;*/ 265 266 } 267 #geetest{ 268 margin-top: 20px; 269 } 270 .register_btn{ 271 width: 100%; 272 height: 45px; 273 background: #84cc39; 274 border-radius: 5px; 275 font-size: 16px; 276 color: #fff; 277 letter-spacing: .26px; 278 margin-top: 30px; 279 } 280 .inp .go_login{ 281 text-align: center; 282 font-size: 14px; 283 color: #9b9b9b; 284 letter-spacing: .26px; 285 padding-top: 20px; 286 } 287 .inp .go_login span{ 288 color: #84cc39; 289 cursor: pointer; 290 } 291 .sms-box{ 292 position: relative; 293 } 294 .sms-btn{ 295 font-size: 14px; 296 color: #ffc210; 297 letter-spacing: .26px; 298 position: absolute; 299 right: 16px; 300 top: 10px; 301 cursor: pointer; 302 overflow: hidden; 303 background: #fff; 304 border-left: 1px solid #484848; 305 padding-left: 16px; 306 padding-bottom: 4px; 307 } 308 </style>
Login.vue
1 <template> 2 <div class="login-box"> 3 <img src="../../static/img/Loginbg.3377d0c.jpg" alt=""> 4 <div class="login"> 5 <div class="login-title"> 6 <img src="../../static/img/Logotitle.1ba5466.png" alt=""> 7 <p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p> 8 </div> 9 <div class="login_box"> 10 <div class="title"> 11 <span @click="login_type=0">密码登录</span> 12 <span @click="login_type=1">短信登录</span> 13 </div> 14 <div class="inp" v-if="login_type==0"> 15 <input v-model = "username" type="text" placeholder="用户名 / 手机号码" class="user"> 16 <input v-model = "password" type="password" name="" class="pwd" placeholder="密码"> 17 <div id="geetest1"></div> 18 <div class="rember"> 19 <p> 20 <input type="checkbox" class="no" v-model="remember"/> 21 <span>记住密码</span> 22 </p> 23 <p>忘记密码</p> 24 </div> 25 <button class="login_btn" @click="loginhander">登录</button> 26 <p class="go_login" >没有账号 <router-link to="/reg">立即注册</router-link></p> 27 </div> 28 <div class="inp" v-show="login_type==1"> 29 <input v-model = "username" type="text" placeholder="手机号码" class="user"> 30 <input v-model = "password" type="text" class="pwd" placeholder="短信验证码"> 31 <button id="get_code">获取验证码</button> 32 <button class="login_btn">登录</button> 33 <p class="go_login" >没有账号 <router-link to="/reg">立即注册</router-link></p> 34 </div> 35 </div> 36 </div> 37 </div> 38 </template> 39 40 <script> 41 export default { 42 name: 'Login', 43 data(){ 44 return { 45 login_type: 0, 46 username:"", 47 password:"", 48 remember:"", 49 is_geek:false, 50 } 51 }, 52 mounted(){ 53 // 请求后端获取生成验证码的流水号 54 this.$axios.get(this.$settings.Host + "/users/captcha/",{ 55 responseType: 'json', // 希望返回json数据 56 }).then(response => { 57 let data = response.data; 58 59 // 验证初始化配置 60 initGeetest({ 61 gt: data.gt, 62 challenge: data.challenge, 63 product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效 64 offline: !data.success 65 },this.handlerPopup) 66 }).catch(error => { 67 console.log(error.response); 68 }); 69 }, 70 methods:{ 71 // 用户登录 72 loginhander(){ 73 // 判断用户是否已经通过了极验验证 74 if(!this.is_geek){ 75 return false; 76 } 77 78 this.$axios.post(this.$settings.Host+"/users/login/",{ 79 username:this.username, 80 password:this.password, 81 }).then(response=>{ 82 let data = response.data 83 // 根据用户是否勾选了记住密码来保存用户认证信息 84 if(this.remember){ 85 // 记住密码 86 localStorage.token = data.token; 87 localStorage.user_id = data.id; 88 localStorage.user_name = data.username; 89 90 }else{ 91 // 不需要记住密码 92 sessionStorage.token = data.token; 93 sessionStorage.user_id = data.id; 94 sessionStorage.user_name = data.username; 95 } 96 97 // 登录成功以后,跳转会上一个页面 98 this.$router.go(-1); 99 100 }).catch(error=>{ 101 console.log(error.response) 102 }) 103 }, 104 // 验证码的成功验证事件方法 105 handlerPopup(captchaObj){ 106 // 把验证码添加到模板中制定的页面 107 captchaObj.appendTo("#geetest1"); 108 109 // 记录vue对象 110 let _this = this; 111 112 // 监听用户对于验证码的操作是否成功了 113 captchaObj.onSuccess(()=>{ 114 var validate = captchaObj.getValidate(); 115 116 _this.$axios.post(_this.$settings.Host+"/users/captcha/",{ 117 geetest_challenge: validate.geetest_challenge, 118 geetest_validate: validate.geetest_validate, 119 geetest_seccode: validate.geetest_seccode 120 }).then(response=>{ 121 // 在用户成功添加数据以后,可以允许点击登录按钮 122 _this.is_geek = true; 123 124 }).catch(error=>{ 125 console.log(error.response) 126 }) 127 128 }); 129 130 }, 131 }, 132 133 }; 134 </script> 135 <style scoped> 136 .login-box{ 137 width: 100%; 138 height: 100%; 139 position: relative; 140 overflow: hidden; 141 margin-top: -80px; 142 } 143 .login-box img{ 144 width: 100%; 145 min-height: 100%; 146 } 147 .login-box .login { 148 position: absolute; 149 width: 500px; 150 height: 400px; 151 left: 0; 152 margin: auto; 153 right: 0; 154 bottom: 0; 155 top: -220px; 156 } 157 .login .login-title{ 158 width: 100%; 159 text-align: center; 160 } 161 .login-title img{ 162 width: 190px; 163 height: auto; 164 } 165 .login-title p{ 166 font-size: 18px; 167 color: #fff; 168 letter-spacing: .29px; 169 padding-top: 10px; 170 padding-bottom: 50px; 171 } 172 .login_box{ 173 width: 400px; 174 height: auto; 175 background: #fff; 176 box-shadow: 0 2px 4px 0 rgba(0,0,0,.5); 177 border-radius: 4px; 178 margin: 0 auto; 179 padding-bottom: 40px; 180 } 181 .login_box .title{ 182 font-size: 20px; 183 color: #9b9b9b; 184 letter-spacing: .32px; 185 border-bottom: 1px solid #e6e6e6; 186 display: flex; 187 justify-content: space-around; 188 padding: 50px 60px 0 60px; 189 margin-bottom: 20px; 190 cursor: pointer; 191 } 192 .login_box .title span:nth-of-type(1){ 193 color: #4a4a4a; 194 border-bottom: 2px solid #84cc39; 195 } 196 197 .inp{ 198 width: 350px; 199 margin: 0 auto; 200 } 201 .inp input{ 202 outline: 0; 203 width: 100%; 204 height: 45px; 205 border-radius: 4px; 206 border: 1px solid #d9d9d9; 207 text-indent: 20px; 208 font-size: 14px; 209 background: #fff !important; 210 } 211 .inp input.user{ 212 margin-bottom: 16px; 213 } 214 .inp .rember{ 215 display: flex; 216 justify-content: space-between; 217 align-items: center; 218 position: relative; 219 margin-top: 10px; 220 } 221 .inp .rember p:first-of-type{ 222 font-size: 12px; 223 color: #4a4a4a; 224 letter-spacing: .19px; 225 margin-left: 22px; 226 display: -ms-flexbox; 227 display: flex; 228 -ms-flex-align: center; 229 align-items: center; 230 /*position: relative;*/ 231 } 232 .inp .rember p:nth-of-type(2){ 233 font-size: 14px; 234 color: #9b9b9b; 235 letter-spacing: .19px; 236 cursor: pointer; 237 } 238 239 .inp .rember input{ 240 outline: 0; 241 width: 30px; 242 height: 45px; 243 border-radius: 4px; 244 border: 1px solid #d9d9d9; 245 text-indent: 20px; 246 font-size: 14px; 247 background: #fff !important; 248 } 249 250 .inp .rember p span{ 251 display: inline-block; 252 font-size: 12px; 253 width: 100px; 254 /*position: absolute;*/ 255 /*left: 20px;*/ 256 257 } 258 #geetest{ 259 margin-top: 20px; 260 } 261 .login_btn{ 262 width: 100%; 263 height: 45px; 264 background: #84cc39; 265 border-radius: 5px; 266 font-size: 16px; 267 color: #fff; 268 letter-spacing: .26px; 269 margin-top: 30px; 270 } 271 .inp .go_login{ 272 text-align: center; 273 font-size: 14px; 274 color: #9b9b9b; 275 letter-spacing: .26px; 276 padding-top: 20px; 277 } 278 .inp .go_login span{ 279 color: #84cc39; 280 cursor: pointer; 281 } 282 </style>
Course.vue
1 <template> 2 <div class="course"> 3 <Header/> 4 <div class="main"> 5 <!-- 筛选功能 --> 6 <div class="top"> 7 <ul class="condition condition1"> 8 <li class="cate-condition">课程分类:</li> 9 <li class="item" :class="query_params.course_category==0?'current':''" @click="query_params.course_category=0">全部</li> 10 <li :class="query_params.course_category==catetory.id?'current':''" @click="query_params.course_category=catetory.id" v-for="catetory in catetory_list" :data-key="catetory.id" class="item">{{catetory.name}}</li> 11 </ul> 12 <ul class="condition condition2"> 13 <li class="cate-condition">筛 选:</li> 14 <li class="item" :class="(query_params.ordering=='-id' || query_params.ordering=='id')?'current':''" @click="select_ordering('id')">默认</li> 15 <li class="item" :class="(query_params.ordering=='-students' || query_params.ordering=='students')?'current':''" @click="select_ordering('students')">人气</li> 16 <li class="item" :class="query_params.ordering=='price'?'current price':(query_params.ordering=='-price'?'current price2':'')" @click="select_ordering('price')">价格</li> 17 <li class="course-length">共21个课程</li> 18 </ul> 19 </div> 20 <!-- 课程列表 ---> 21 <div class="list"> 22 <ul> 23 <li class="course-item" v-for="course in course_list"> 24 <router-link :to="{path: '/detail',query:{id:course.id}}" class="course-link"> 25 <div class="course-cover"> 26 <img :src="course.course_img" alt=""> 27 </div> 28 <div class="course-info"> 29 <div class="course-title"> 30 <h3>{{course.name}}</h3> 31 <span>{{course.students}}人已加入学习</span> 32 </div> 33 <p class="teacher"> 34 <span class="info">{{course.teacher.name}} {{course.teacher.title}}</span> 35 <span class="lesson">共{{course.lessons}}课时/{{course.lessons==course.pub_lessons?'更新完成':('已更新'+course.pub_lessons+"课时")}}</span> 36 </p> 37 <ul class="lesson-list"> 38 <li v-for="lesson,key in course.lesson_list"> 39 <p class="lesson-title">0{{key+1}} | {{lesson.name}}</p> 40 <span v-if="lesson.free_trail" class="free">免费</span> 41 </li> 42 43 </ul> 44 <div class="buy-info"> 45 <div v-if="course.get_course_discount_type"> 46 <span class="discount">{{course.get_course_discount_type}}</span> 47 <span class="present-price">¥{{course.get_course_price}}元</span> 48 <span class="original-price">原价:{{course.price}}元</span> 49 </div> 50 <span v-else class="present-price">¥{{course.price}}元</span> 51 <button class="buy-now">立即购买</button> 52 </div> 53 </div> 54 </router-link> 55 </li> 56 </ul> 57 </div> 58 <div class="pagination"> 59 <el-pagination 60 @current-change="handleCurrentChange" 61 :current-page="query_params.current_page" 62 background 63 layout="prev, pager, next" 64 :page-size="course_page_size" 65 :total="course_count"> 66 </el-pagination> 67 </div> 68 </div> 69 <Footer/> 70 </div> 71 </template> 72 73 <script> 74 import Header from "./common/Header" 75 import Footer from "./common/Footer" 76 export default { 77 name: "Course", 78 data(){ 79 return { 80 catetory_list:[], 81 course_list:[], 82 course_count: 0, 83 course_page_size:1, 84 query_params:{ 85 course_category: 0, 86 ordering:"-id", 87 current_page: 1, 88 } 89 } 90 }, 91 watch:{ 92 // 每次点击不同课程时,要重新获取课程列表 93 "query_params.course_category":function(){ 94 this.get_course_list(); 95 // 当切换分类的时候,重置页码 96 this.query_params.current_page = 1; 97 }, 98 "query_params.ordering":function(){ 99 // 当切换排序条件的时候,重置页码 100 // this.query_params.current_page = 1; 101 this.get_course_list(); 102 }, 103 "query_params.current_page":function(){ 104 this.get_course_list(); 105 } 106 }, 107 components: {Header, Footer}, 108 created(){ 109 // 获取课程分类 110 this.$axios.get(this.$settings.Host+"/courses/cate/").then(response=>{ 111 this.catetory_list = response.data 112 }).catch(error=>{ 113 console.log(error.response) 114 }); 115 116 // 获取课程信息 117 this.get_course_list() 118 119 }, 120 methods:{ 121 select_ordering(selector){ 122 // 默认排序 123 if(this.query_params.ordering==('-'+selector) ){ 124 this.query_params.ordering = selector; 125 }else{ 126 this.query_params.ordering = '-'+selector; 127 } 128 }, 129 get_course_list(){ 130 let query_params = { 131 ordering:this.query_params.ordering, 132 page:this.query_params.current_page, 133 }; 134 135 if( this.query_params.course_category != 0 ){ 136 query_params.course_category = this.query_params.course_category; 137 } 138 139 this.$axios.get(this.$settings.Host+"/courses/list/",{ 140 params: query_params 141 }).then(response=>{ 142 // 课程列表 143 this.course_list = response.data.results; 144 // 课程总数量 145 this.course_count = response.data.count; 146 147 }).catch(error=>{ 148 console.log(error.response) 149 }); 150 }, 151 handleCurrentChange(page){ 152 // 页码发生改变 153 this.query_params.current_page = page; 154 } 155 } 156 } 157 </script> 158 159 <style scoped> 160 .main{ 161 width: 1100px; 162 height: auto; 163 margin: 0 auto; 164 padding-top: 35px; 165 } 166 .main .top{ 167 margin-bottom: 35px; 168 padding: 25px 30px 25px 20px; 169 background: #fff; 170 border-radius: 4px; 171 box-shadow: 0 2px 4px 0 #f0f0f0; 172 } 173 .condition{ 174 border-bottom: 1px solid #333; 175 border-bottom-color: rgba(51,51,51,.05); 176 padding-bottom: 18px; 177 margin-bottom: 17px; 178 overflow: hidden; 179 } 180 .condition li{ 181 float: left; 182 } 183 .condition .cate-condition{ 184 color: #888; 185 font-size: 16px; 186 } 187 .condition .item{ 188 padding: 6px 16px; 189 line-height: 16px; 190 margin-left: 14px; 191 position: relative; 192 transition: all .3s ease; 193 border:1px solid transparent; /* transparent 透明 */ 194 cursor: pointer; 195 color: #4a4a4a; 196 } 197 .condition1 .current{ 198 color: #ffc210; 199 border: 1px solid #ffc210!important; 200 border-radius: 30px; 201 } 202 .condition2 .current{ 203 color: #ffc210; 204 } 205 .condition .price:before{ 206 content: ""; 207 width: 0; 208 border: 5px solid transparent; 209 border-top-color: #d8d8d8; 210 position: absolute; 211 right: 0; 212 bottom: 2.5px; 213 } 214 .condition .price2:before{ 215 content: ""; 216 width: 0; 217 border: 5px solid transparent; 218 position: absolute; 219 right: 0; 220 bottom: 2.5px; 221 border-top-color: #ffc210; 222 } 223 .condition .price2:after{ 224 content: ""; 225 width: 0; 226 border: 5px solid transparent; 227 position: absolute; 228 right: 0; 229 top: 2.5px; 230 border-bottom-color: #d8d8d8; 231 } 232 .condition .price:after{ 233 content: ""; 234 width: 0; 235 border: 5px solid transparent; 236 border-bottom-color: #ffc210; 237 position: absolute; 238 right: 0; 239 top: 2.5px; 240 } 241 .condition2 .course-length{ 242 float: right; 243 font-size: 14px; 244 color: #9b9b9b; 245 } 246 .course-item{ 247 background: #fff; 248 padding: 20px 30px 20px 20px; 249 margin-bottom: 35px; 250 border-radius: 2px; 251 cursor: pointer; 252 box-shadow: 2px 3px 16px rgba(0,0,0,.1); 253 transition: all .2s ease; 254 overflow: hidden; 255 cursor:pointer; 256 } 257 .course-link{ 258 overflow: hidden; 259 } 260 .course-cover { 261 width: 423px; 262 height: 210px; 263 margin-right: 30px; 264 float: left; 265 } 266 .course-info{ 267 width: 597px; 268 float: left; 269 } 270 .course-title{ 271 margin-bottom: 8px; 272 overflow: hidden; 273 274 } 275 .course-title h3{ 276 font-size: 26px; 277 color: #333; 278 float: left; 279 } 280 .course-title span { 281 float: right; 282 font-size: 14px; 283 color: #9b9b9b; 284 margin-top: 12px; 285 text-indent: 1em; /* 缩进 2字符宽度 */ 286 background: url("../assets/people.svg") no-repeat 0px 3px; 287 } 288 .teacher{ 289 justify-content: space-between; 290 font-size: 14px; 291 color: #9b9b9b; 292 margin-bottom: 14px; 293 padding-bottom: 14px; 294 border-bottom: 1px solid #333; 295 border-bottom-color: rgba(51,51,51,.05); 296 } 297 .teacher .lesson{ 298 float: right; 299 } 300 .lesson-list{ 301 overflow: hidden; 302 } 303 .lesson-list li{ 304 width: 49%; 305 margin-bottom: 15px; 306 cursor: pointer; 307 float: left; 308 margin-right:1%; 309 } 310 .lesson-list li .player{ 311 width: 16px; 312 height: 16px; 313 vertical-align: text-bottom; 314 } 315 .lesson-list li .lesson-title { 316 display: inline-block; 317 max-width: 227px; 318 text-overflow: ellipsis; /* 如果字体太多超出元素的宽度,则添加省略符号 */ 319 color: #666; 320 overflow: hidden; 321 white-space: nowrap; 322 font-size: 14px; 323 vertical-align: text-bottom; /* 文本的垂直对齐方式: text-botton 文本底部对齐 */ 324 text-indent: 1.5em; 325 background: url(../../static/player.svg) no-repeat 0px 3px; 326 } 327 328 .lesson-list .free{ 329 width: 34px; 330 height: 20px; 331 color: #fd7b4d; 332 margin-left: 10px; 333 border: 1px solid #fd7b4d; 334 border-radius: 2px; 335 text-align: center; 336 font-size: 13px; 337 white-space: nowrap; 338 } 339 .lesson-list li:hover .lesson-title{ 340 color: #ffc210; 341 background-image: url(../../static/player2.svg); 342 } 343 .lesson-list li:hover .free{ 344 border-color: #ffc210; 345 color: #ffc210; 346 } 347 348 .buy-info .discount{ 349 padding: 0px 10px; 350 font-size: 16px; 351 color: #fff; 352 display: inline-block; 353 height: 36px; 354 text-align: center; 355 margin-right: 8px; 356 background: #fa6240; 357 border: 1px solid #fa6240; 358 border-radius: 10px 0 10px 0; 359 line-height: 36px; 360 } 361 .present-price{ 362 font-size: 24px; 363 color: #fa6240; 364 } 365 .original-price{ 366 text-decoration: line-through; 367 font-size: 14px; 368 color: #9b9b9b; 369 margin-left: 10px; 370 } 371 .buy-now{ 372 width: 120px; 373 height: 38px; 374 background: transparent; 375 color: #fa6240; 376 font-size: 16px; 377 border: 1px solid #fd7b4d; 378 border-radius: 3px; 379 transition: all .2s ease-in-out; /* 过渡动画 */ 380 float: right; 381 margin-top: 5px; 382 } 383 .buy-now:hover{ 384 color: #fff; 385 background: #ffc210; 386 border: 1px solid #ffc210; 387 cursor: pointer; 388 } 389 .pagination{ 390 text-align: center; 391 margin: 20px 0px 50px 0px; 392 } 393 </style>
Detail.vue
1 <template> 2 <div class="detail"> 3 <Header/> 4 <div class="main"> 5 <div class="course-info"> 6 <div class="wrap-left"> 7 <video-player class="video-player vjs-custom-skin" 8 ref="videoPlayer" 9 :playsinline="true" 10 :options="playerOptions" 11 @play="onPlayerPlay($event)" 12 @pause="onPlayerPause($event)" 13 > 14 </video-player> 15 </div> 16 <div class="wrap-right"> 17 <h3 class="course-name">{{course.name}}</h3> 18 <p class="data">{{course.students}}人在学 课程总时长:{{course.lessons}}课时/{{course.lessons==course.pub_lessons?'更新完成':('已更新'+course.pub_lessons+"课程")}} 难度:{{course.course_level}}</p> 19 <div v-if="course.get_course_discount_type"> 20 <div class="sale-time"> 21 <p class="sale-type">{{course.get_course_discount_type}}</p> 22 <p class="expire">距离结束:仅剩 {{Math.floor(course.has_time/86400)}}天 {{Math.floor(course.has_time%86400/3600)}}小时 {{Math.floor(course.has_time%86400%3600/60)}}分 <span class="second">{{Math.floor(course.has_time%86400%3600%60)}}</span> 秒</p> 23 </div> 24 <p class="course-price"> 25 <span>活动价</span> 26 <span class="discount">¥{{course.get_course_price}}</span> 27 <span class="original">¥{{course.price}}</span> 28 </p> 29 </div> 30 <div v-else> 31 <div class="sale-time"> 32 <p class="sale-type">价格: ¥{{course.price}}</p> 33 </div> 34 </div> 35 <div class="buy"> 36 <div class="buy-btn"> 37 <button class="buy-now">立即购买</button> 38 <button class="free">免费试学</button> 39 </div> 40 <div @click="cartAddHander" class="add-cart"><img src="@/assets/cart-yellow.svg" alt="">加入购物车</div> 41 </div> 42 </div> 43 </div> 44 <div class="course-tab"> 45 <ul class="tab-list"> 46 <li :class="tabIndex==1?'active':''" @click="tabIndex=1">详情介绍</li> 47 <li :class="tabIndex==2?'active':''" @click="tabIndex=2">课程章节 <span :class="tabIndex!=2?'free':''">(试学)</span></li> 48 <li :class="tabIndex==3?'active':''" @click="tabIndex=3">用户评论 (42)</li> 49 <li :class="tabIndex==4?'active':''" @click="tabIndex=4">常见问题</li> 50 </ul> 51 </div> 52 <div class="course-content"> 53 <div class="course-tab-list"> 54 <div class="tab-item" v-if="tabIndex==1"> 55 <div v-html="course.brief"></div> 56 </div> 57 <div class="tab-item" v-if="tabIndex==2"> 58 <div class="tab-item-title"> 59 <p class="chapter">课程章节</p> 60 <p class="chapter-length">共{{chapter_list.length}}章 147个课时</p> 61 </div> 62 <div class="chapter-item" v-for="chapter in chapter_list"> 63 <p class="chapter-title"><img src="@/assets/1.svg" alt="">第{{chapter.chapter}}章·{{chapter.name}}</p> 64 <ul class="lesson-list"> 65 <li class="lesson-item" v-for="lesson in chapter.coursesections"> 66 <p class="name"><span class="index">{{chapter.chapter}}-{{lesson.id}}</span> {{lesson.name}}<span class="free" v-if="lesson.free_trail">免费</span></p> 67 <p class="time">{{lesson.duration}} <img src="@/assets/chapter-player.svg"></p> 68 <button class="try" v-if="lesson.free_trail"><router-link :to="{path: '/player',query:{'vid':lesson.section_link}}">立即试学</router-link></button> 69 <button class="try" v-else>立即购买</button> 70 </li> 71 72 </ul> 73 </div> 74 </div> 75 <div class="tab-item" v-if="tabIndex==3"> 76 用户评论 77 </div> 78 <div class="tab-item" v-if="tabIndex==4"> 79 常见问题 80 </div> 81 </div> 82 <div class="course-side"> 83 <div class="teacher-info"> 84 <h4 class="side-title"><span>授课老师</span></h4> 85 <div class="teacher-content"> 86 <div class="cont1"> 87 <img :src="course.teacher.image"> 88 <div class="name"> 89 <p class="teacher-name">{{course.teacher.name}} {{course.teacher.title}}</p> 90 <p class="teacher-title">{{course.teacher.signature}}</p> 91 </div> 92 </div> 93 <p class="narrative" >Linux运维技术专家,老男孩Linux金牌讲师,讲课风趣幽默、深入浅出、声音洪亮到爆炸</p> 94 </div> 95 </div> 96 </div> 97 </div> 98 </div> 99 <Footer/> 100 </div> 101 </template> 102 103 <script> 104 import Header from "./common/Header" 105 import Footer from "./common/Footer" 106 107 import {videoPlayer} from 'vue-video-player'; 108 109 export default { 110 name: "Detail", 111 data(){ 112 return { 113 token:sessionStorage.token || localStorage.token, 114 user_id:sessionStorage.user_id || localStorage.user_id, 115 user_name:sessionStorage.user_name || localStorage.user_name, 116 tabIndex:1, // 当前选项卡显示的下标 117 course_id:0, // 当前页面对应的课程ID 118 course: { 119 teacher: {}, 120 }, // 课程详情信息 121 chapter_list:{}, 122 playerOptions: { 123 playbackRates: [0.7, 1.0, 1.5, 2.0], // 播放速度 124 autoplay: false, //如果true,则自动播放 125 muted: false, // 默认情况下将会消除任何音频。 126 loop: false, // 循环播放 127 preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) 128 language: 'zh-CN', 129 aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") 130 fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 131 sources: [{ // 播放资源和资源格式 132 type: "video/mp4", 133 src: "" //你的视频地址(必填) 134 }], 135 poster: "../static/courses/675076.jpeg", //视频封面图 136 width: document.documentElement.clientWidth, // 默认视频全屏时的最大宽度 137 notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。 138 } 139 } 140 }, 141 watch:{ 142 course(data){ 143 // // 替换视频地址 144 // this.playerOptions.sources[0].src = data.video; 145 // // 替换视频封面 146 // this.playerOptions.poster = data.course_img; 147 // 替换科恒信息中的详情介绍里面的图片路径 148 while(data.brief.search(`"/media`) != -1 ){ 149 data.brief = data.brief.replace(`"/media`,`"${this.$settings.Host}/media`) 150 } 151 }, 152 tabIndex(data){ 153 if(data==2){ 154 //获取当前课程对应的章节列表和课时列表 155 this.$axios.get(`${this.$settings.Host}/courses/chapters/?course=${this.course_id}`).then(response=>{ 156 this.chapter_list = response.data; 157 }).catch(error=>{ 158 console.log(error.response); 159 }) 160 } 161 } 162 }, 163 created(){ 164 // 获取当前课程ID 165 this.course_id = this.$route.query.id - 0; 166 // 判断ID基本有效性 167 let _this = this; 168 if( isNaN(this.course_id) || this.course_id < 1 ){ 169 _this.$alert("无效的课程ID!","错误",{ 170 callback(){ 171 _this.$router.go(-1); 172 }}); 173 } 174 // 发送请求获取后端课程数据 175 this.$axios.get(this.$settings.Host+`/courses/detail/${this.course_id}/`).then(response=>{ 176 this.course = response.data; 177 // 修改视频中的封面图片 178 this.playerOptions.poster = this.course.course_img; 179 this.playerOptions.sources[0].src = this.course.video; 180 // 倒计时 181 if(this.course.has_time > 1){ 182 183 let timer = setInterval(()=>{ 184 if( this.course.has_time > 1 ){ 185 this.course.has_time-=1; 186 }else{ 187 clearInterval(timer); 188 location.reload(); 189 } 190 },1000); 191 192 } 193 194 }).catch(error=>{ 195 console.log(error.response) 196 }); 197 }, 198 methods: { 199 // 视频播放事件 200 onPlayerPlay(player) { 201 alert("play"); 202 }, 203 // 视频暂停播放事件 204 onPlayerPause(player){ 205 alert("pause"); 206 }, 207 // 视频插件初始化 208 player() { 209 return this.$refs.videoPlayer.player; 210 }, 211 // 添加商品课程到购物车 212 cartAddHander(){ 213 // 1. 判断用户是否已经登录了. 214 this.token = sessionStorage.token || localStorage.token; 215 if( !this.token ){ 216 this.$confirm("对不起,您尚未登录!请登录",'提示').then(() => { 217 this.$router.push("/login"); 218 }); 219 }else{ 220 221 // 2. 发起请求 222 this.$axios.post(this.$settings.Host+`/carts/course/`,{ 223 course_id: this.course_id, 224 },{ 225 headers:{ 226 // 注意:jwt后面必须有且只有一个空格!!!! 227 "Authorization":"jwt " + this.token 228 } 229 }).then(response=>{ 230 231 // 获取购物城中商品总数 232 // this.$store.state.cart.count = response.data.count; 233 this.$store.commit("addcart",response.data); 234 // 添加购物车成功! 235 this.$message(response.data.message,"提示!",{ 236 duration: 2000, // 单位: 毫秒 237 }); 238 239 }).catch(error=>{ 240 241 console.log(error.response); 242 }) 243 } 244 } 245 }, 246 components:{ 247 Header, 248 Footer, 249 videoPlayer, 250 } 251 } 252 </script> 253 254 <style scoped> 255 .main{ 256 background: #fff; 257 padding-top: 30px; 258 } 259 .course-info{ 260 width: 1200px; 261 margin: 0 auto; 262 overflow: hidden; 263 } 264 .wrap-left{ 265 float: left; 266 width: 690px; 267 height: 388px; 268 background-color: #000; 269 } 270 .wrap-right{ 271 float: left; 272 position: relative; 273 height: 388px; 274 } 275 .course-name{ 276 font-size: 20px; 277 color: #333; 278 padding: 10px 23px; 279 letter-spacing: .45px; 280 } 281 .data{ 282 padding-left: 23px; 283 padding-right: 23px; 284 padding-bottom: 16px; 285 font-size: 14px; 286 color: #9b9b9b; 287 } 288 .sale-time{ 289 width: 464px; 290 background: #fa6240; 291 font-size: 14px; 292 color: #4a4a4a; 293 padding: 10px 23px; 294 overflow: hidden; 295 } 296 .sale-type { 297 font-size: 16px; 298 color: #fff; 299 letter-spacing: .36px; 300 float: left; 301 } 302 .sale-time .expire{ 303 font-size: 14px; 304 color: #fff; 305 float: right; 306 } 307 .sale-time .expire .second{ 308 width: 24px; 309 display: inline-block; 310 background: #fafafa; 311 color: #5e5e5e; 312 padding: 6px 0; 313 text-align: center; 314 } 315 .course-price{ 316 background: #fff; 317 font-size: 14px; 318 color: #4a4a4a; 319 padding: 5px 23px; 320 } 321 .discount{ 322 font-size: 26px; 323 color: #fa6240; 324 margin-left: 10px; 325 display: inline-block; 326 margin-bottom: -5px; 327 } 328 .original{ 329 font-size: 14px; 330 color: #9b9b9b; 331 margin-left: 10px; 332 text-decoration: line-through; 333 } 334 .buy{ 335 width: 464px; 336 padding: 0px 23px; 337 position: absolute; 338 left: 0; 339 bottom: 20px; 340 overflow: hidden; 341 } 342 .buy .buy-btn{ 343 float: left; 344 } 345 .buy .buy-now{ 346 width: 125px; 347 height: 40px; 348 border: 0; 349 background: #ffc210; 350 border-radius: 4px; 351 color: #fff; 352 cursor: pointer; 353 margin-right: 15px; 354 outline: none; 355 } 356 .buy .free{ 357 width: 125px; 358 height: 40px; 359 border-radius: 4px; 360 cursor: pointer; 361 margin-right: 15px; 362 background: #fff; 363 color: #ffc210; 364 border: 1px solid #ffc210; 365 } 366 .add-cart{ 367 float: right; 368 font-size: 14px; 369 color: #ffc210; 370 text-align: center; 371 cursor: pointer; 372 margin-top: 10px; 373 } 374 .add-cart img{ 375 width: 20px; 376 height: 18px; 377 margin-right: 7px; 378 vertical-align: middle; 379 } 380 381 .course-tab{ 382 width: 100%; 383 background: #fff; 384 margin-bottom: 30px; 385 box-shadow: 0 2px 4px 0 #f0f0f0; 386 387 } 388 .course-tab .tab-list{ 389 width: 1200px; 390 margin: auto; 391 color: #4a4a4a; 392 overflow: hidden; 393 } 394 .tab-list li{ 395 float: left; 396 margin-right: 15px; 397 padding: 26px 20px 16px; 398 font-size: 17px; 399 cursor: pointer; 400 } 401 .tab-list .active{ 402 color: #ffc210; 403 border-bottom: 2px solid #ffc210; 404 } 405 .tab-list .free{ 406 color: #fb7c55; 407 } 408 .course-content{ 409 width: 1200px; 410 margin: 0 auto; 411 background: #FAFAFA; 412 overflow: hidden; 413 padding-bottom: 40px; 414 } 415 .course-tab-list{ 416 width: 880px; 417 height: auto; 418 padding: 20px; 419 background: #fff; 420 float: left; 421 box-sizing: border-box; 422 overflow: hidden; 423 position: relative; 424 box-shadow: 0 2px 4px 0 #f0f0f0; 425 } 426 .tab-item{ 427 width: 880px; 428 background: #fff; 429 padding-bottom: 20px; 430 box-shadow: 0 2px 4px 0 #f0f0f0; 431 } 432 .tab-item-title{ 433 justify-content: space-between; 434 padding: 25px 20px 11px; 435 border-radius: 4px; 436 margin-bottom: 20px; 437 border-bottom: 1px solid #333; 438 border-bottom-color: rgba(51,51,51,.05); 439 overflow: hidden; 440 } 441 .chapter{ 442 font-size: 17px; 443 color: #4a4a4a; 444 float: left; 445 } 446 .chapter-length{ 447 float: right; 448 font-size: 14px; 449 color: #9b9b9b; 450 letter-spacing: .19px; 451 } 452 .chapter-title{ 453 font-size: 16px; 454 color: #4a4a4a; 455 letter-spacing: .26px; 456 padding: 12px; 457 background: #eee; 458 border-radius: 2px; 459 display: -ms-flexbox; 460 display: flex; 461 -ms-flex-align: center; 462 align-items: center; 463 } 464 .chapter-title img{ 465 width: 18px; 466 height: 18px; 467 margin-right: 7px; 468 vertical-align: middle; 469 } 470 .lesson-list{ 471 padding:0 20px; 472 } 473 .lesson-list .lesson-item{ 474 padding: 15px 20px 15px 36px; 475 cursor: pointer; 476 justify-content: space-between; 477 position: relative; 478 overflow: hidden; 479 } 480 .lesson-item .name{ 481 font-size: 14px; 482 color: #666; 483 float: left; 484 } 485 .lesson-item .index{ 486 margin-right: 5px; 487 } 488 .lesson-item .free{ 489 font-size: 12px; 490 color: #fff; 491 letter-spacing: .19px; 492 background: #ffc210; 493 border-radius: 100px; 494 padding: 1px 9px; 495 margin-left: 10px; 496 } 497 .lesson-item .time{ 498 font-size: 14px; 499 color: #666; 500 letter-spacing: .23px; 501 opacity: 1; 502 transition: all .15s ease-in-out; 503 float: right; 504 } 505 .lesson-item .time img{ 506 width: 18px; 507 height: 18px; 508 margin-left: 15px; 509 vertical-align: text-bottom; 510 } 511 .lesson-item .try{ 512 width: 86px; 513 height: 28px; 514 background: #ffc210; 515 border-radius: 4px; 516 font-size: 14px; 517 color: #fff; 518 position: absolute; 519 right: 20px; 520 top: 10px; 521 opacity: 0; 522 transition: all .2s ease-in-out; 523 cursor: pointer; 524 outline: none; 525 border: none; 526 } 527 .lesson-item:hover{ 528 background: #fcf7ef; 529 box-shadow: 0 0 0 0 #f3f3f3; 530 } 531 .lesson-item:hover .name{ 532 color: #333; 533 } 534 .lesson-item:hover .try{ 535 opacity: 1; 536 } 537 538 .course-side{ 539 width: 300px; 540 height: auto; 541 margin-left: 20px; 542 float: right; 543 } 544 .teacher-info{ 545 background: #fff; 546 margin-bottom: 20px; 547 box-shadow: 0 2px 4px 0 #f0f0f0; 548 } 549 .side-title{ 550 font-weight: normal; 551 font-size: 17px; 552 color: #4a4a4a; 553 padding: 18px 14px; 554 border-bottom: 1px solid #333; 555 border-bottom-color: rgba(51,51,51,.05); 556 } 557 .side-title span{ 558 display: inline-block; 559 border-left: 2px solid #ffc210; 560 padding-left: 12px; 561 } 562 563 .teacher-content{ 564 padding: 30px 20px; 565 box-sizing: border-box; 566 } 567 568 .teacher-content .cont1{ 569 margin-bottom: 12px; 570 overflow: hidden; 571 } 572 573 .teacher-content .cont1 img{ 574 width: 54px; 575 height: 54px; 576 margin-right: 12px; 577 float: left; 578 } 579 .teacher-content .cont1 .name{ 580 float: right; 581 } 582 .teacher-content .cont1 .teacher-name{ 583 width: 188px; 584 font-size: 16px; 585 color: #4a4a4a; 586 padding-bottom: 4px; 587 } 588 .teacher-content .cont1 .teacher-title{ 589 width: 188px; 590 font-size: 13px; 591 color: #9b9b9b; 592 white-space: nowrap; 593 } 594 .teacher-content .narrative{ 595 font-size: 14px; 596 color: #666; 597 line-height: 24px; 598 } 599 </style>