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                   &nbsp;&nbsp;|&nbsp;&nbsp;
 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">筛&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</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="">重要!微信扫码关注获得学习通知&amp;课程更新提醒!否则将严重影响学习进度和课程体验!</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">筛&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</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}}人在学&nbsp;&nbsp;&nbsp;&nbsp;课程总时长:{{course.lessons}}课时/{{course.lessons==course.pub_lessons?'更新完成':('已更新'+course.pub_lessons+"课程")}}&nbsp;&nbsp;&nbsp;&nbsp;难度:{{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>

 

posted @ 2019-08-04 22:58  码崽  阅读(379)  评论(0编辑  收藏  举报