Note source: Hook Education – big front end employment Training camp

Content: Notes, thoughts and experiences in the process of learning

Study, course details, class video

Learning function

Layout processing

Use the common bottom navigation bar

Use Vant’s top navigation bar component

SRC/views/study/index. Vue add a navigation bar at the top and bottom < template > < div class = "study" > <! --> <van-nav-bar title=" Purchased course "/> <! <footBar></footBar> </div> </template> <script> import footBar from '@/common/foot-bar' export Default {name: 'study', // register components: {footBar}} </script>Copy the code

Common component handling

The course list and course selection functions are the same, and can share the same component, encapsulated as a common component

Learn how to use components for functionality and tweak some styles

Move the course list component under your course selection into the common component oyster CommonCopy the code
SRC/views/course/index. Vue modify course list drinkable address / / incoming list components import courseList from '@ / common/course - list'Copy the code
SRC/views/study/index. The vue page after the use of mobile learning course list component, and adjust the style... <script> .............. / / introduction course list components import courseList from '@ / common/course - list'... </script> <style lang=" SCSS "scoped> // Adjust the whole course list. Van-pull-refresh {top: 46px; bottom: 50px; } </style>Copy the code

Abstract interface

The interface is passed to the child component in the same way that the parent component passes a value to the child component, and the child component can use it

// SRC/API /course

// Get the purchased course
export const getPurchaseCourse = () = > {
  return axios({
    method: 'get'.url: '/front/course/getPurchaseCourse'})}Copy the code
SRC/views/study/index learning vue interface introducing the interface and the interface is passed to the child components < template > < div class = "study" >... <! - to create course list component instance - > < courseList: getData = "getData" / >... </div> </template> <script> // Import interface: Get purchased course import {getPurchaseCourse} from '@/ API /course'............Copy the code
SRC/views/course/index. Vue course selection interface also convey interface to subcomponents <! <courseList :getData="getData" /> // Import interface: Import {getAllAds, getQueryCourses} from '@/ API /course'Copy the code
SRC /common/course-list.vue: SRC /common/course-list.vue: SRC /common/course-list.vue: SRC /common/course-list.vue: SRC /common/course-list.vue: <template> <van-pull-refresh V-model ="refreshing" @refresh="onRefresh"> <! <van-list v-model="loading" :finished="finished" finished-text=" no more "@load="onLoad" > <! <van-cell v-for="item in listData" :key="item.id"> <! - on the left side of the picture, because two interfaces return data name is different, use | | value judgment - > < img: SRC = "item. CourseImgUrl | | item. The image" Alt = "" > <! - the right information - > < div class = "r" > < h3 > {{item. CourseName | | item. The name}} < / h3 > < p class="FirstField">{{item.previewFirstField}}</p> <! <p v-if="item.price"> <span class="discounts">¥{{item.discounts} </span> <! - directly using the strikethrough - > < s > selections {{item. Price}} < / s > < / p > < / div > < / van - cell > < / van - list > < / van - pull - refresh > < / template > < script > Export default {name: 'courseList', // Props: {// getData: {type: Function, // Type of Function required: }}, data () {return {finished: false, // [], // listData: [], // listData: [], // listData: 1, // {// async onRefresh () {this.currentPage = 1 This. Finished = this.getdata. name === 'getPurchaseCourse' // calls the interface passed by the parent component to getData const {data  } = await this.getData({ currentPage: this.currentPage, pageSize: 10, status: If (data.code === '000000' && data.data.records! This.listdata = data.data.records // number of pages +1 this.currentPage++ // Change the pull-down refresh signal value This. Refreshing = false // this.$toast(' refreshing successfully ')} else if (data.content) {this This. Refreshing = false // This.$toast(' refresh successfully ')}}, // Async onLoad () {const {data} = await this.getData({currentPage:}) const {data} = await this.getData({currentPage:}) If (data.code === '000000' && data.data.records! == 0) {// Get the list of courses successfully // Add data to list this.listdata.push (... Data.data.records) // Number of pages +1 this.currentPage++ if (this.listdata.length >= data.data.total) {// If the list length is greater than or equal to the total number of entries, This. Finished = true}} else if (data.content) {// Add data to the list. Because this interface returns data that is not paginated) this.listdata.push (... This. finished = true} this.loading = false}} </script> <style lang=" SCSS "scoped> // list overall positioning. Van-pull-refresh {position: fixed; left: 0; right: 0; overflow-y: auto; Van-cell. van-cell__value {display: flex; // clear the default effect h3, p {margin: 0; } img { height: 100px; width: 75px; border-radius: 5px; } .r { display: flex; flex-direction: column; margin-left: 10px; .FirstField { flex-grow: 1; } p .discounts { margin-right: 10px; color: #ff7452; } p s { color: #ccc; } } } } </style>Copy the code

Encapsulate interfaces and data binding

Use the Retrieve purchased courses interface

Bind data, but the interface returns a different data name

Using data for | | judgment, points to obtain different data, prices don’t need to be loaded, use the v – if judge whether there is a price data

We’ve taken care of that

Course details

Both the course selection page and the study page support the jump to the details page

Component to prepare

Course details are separate components that do not require a login to access

Course details need to be displayed according to course ids. Dynamic routing parameters and path parameter transmission are required

Click on the list item to jump to the details page with the course ID – Params

// SRC /router/index.js add new routing - course details

{ // Course details
    name: 'course-info'.path: '/course-info/:courseId'.component: () = > import(/* webpackChunkName: 'course-info' */'@/views/course-info'),
    // Set path parameters
    props: true
}
Copy the code
SRC /common/course list. Vue Course List Settings Click on the event to jump to the course details page with the course ID <! -- List item, > <van-cell v-for="item in listData" :key="item.id" @click="$router.push(`/course-info/${item.id}`)">Copy the code
<template> <div>{{courseId}}</div> </template> <script> export default { Name: 'course-info', // Receiving parameter props: {// Path parameter courseId: {type: [Number, String], Required: true}}}Copy the code

Interface encapsulation

Use the Get Course Details interface to get data when the page is initialized

Store the obtained data for later use

// SRC/API /course.js encapsulates the interface to get course details
// Get course details
export const getCourseById = courseId= > {
  return axios({
    method: 'get'.url: '/front/course/getCourseById'.params: {
      courseId
    }
  })
}
Copy the code
<template> <div>{{courseId}}</div> </template> <script> Import {getCourseById} from '@/ API /course' export default {name: 'coursebyID ', // props: {// Path parameters courseId: {type: [Number, String], Required: true}}, data () {return {// Course details info: // Create () {// call this.getcourseinfo ()}, methods: Async getCourseInfo () {const {data} = await getCourseById(this.courseid) console.log(data)}} </script>Copy the code

Topic content area – top

You can use the Vant cell components you used earlier

Add structure binding data for styling

SRC /views/course-info/index.vue Set top image, middle text message, adjust style <template> <! -- cell, all content is distributed in a cell --> <van-cell-group> <! -- Top picture display area, <van-cell class="course-image"> <van-image width="375px" height="280px" fit="fill" : SRC =" info.courseimgURL "/> </van-cell> <! - course text messages - > < van - cell class = "info - the text" > < h2 > {{info. CourseName}} < / h2 > < p > {{info. PreviewFirstField}} < / p > < p Class ="info-text-buy"> <span class="l"> < I class="discounts">¥{{info.discounts}}</ I > < I Class = "price" > selections {{info. The price}} < / I > < / span > < span class = "r" > < I > {{info. Sales}} people already bought < / I > < I > every Wednesday and Friday update < / I > < / span > < / p > </van-cell> </van-cell-group> </template> <script> // Import interface: Import {getCourseById} from '@/ API /course' export default {name: 'coursebyID ', // props: {// Path parameters courseId: {type: [Number, String], Required: true}}, data () {return {// Course details info: // Create () {// call this.getcourseinfo ()}, methods: Async getCourseInfo () {const {data} = await getCourseById(this.courseId) console.log(data) // Data this.info = data.content}}} </script> <style lang=" SCSS "scoped .van-hairline--top-bottom::after, .van-cell::after { display: none; } // Clear the cell component by default. Van-cell {padding: 0; } // top image. course-image {height: 280px; } // Middle text info. Info-text {padding: 10px 20px; .info-text-buy { display: flex; justify-content: space-between; } .discounts { margin-right: 5px; font-size: 24px; color: #ff7452; font-weight: 700; } .r { line-height: 28px; font-size: 12px; color: #666; i { padding: 7px 8px; border-radius: 5px; margin-left: 10px; background: #f8f9fa; font-weight: 700; } } } </style>Copy the code

TAB processing

Using Vant’s TAB component, turn on sticky layouts that automatically snap to the top after switching, and use scroll navigation properties

Add structure binding data, handle styles

Course details are saved in the background by a rich text editor, so you can set them directly using V-HTML

SRC /views/course-info/index.vue <van-cell class="tabs"> <! <van-tabs scrollSpy sticky> <! <van- TAB title=" van- TAB "> <! - bound data - > < div v - HTML = "info. CourseDescription" > < / div > < / van - TAB > <! - content - > < van - TAB title = "content" > 123 < / van - TAB > < / van - tabs > < / van - cell > < / van - cell - group >Copy the code

Chapter Component Encapsulation

Use the Get Course Chapter interface

Separate package a chapter display component, separate chapter package into a component, including chapter name, class

Components use V-for internally to create structures

So let’s write it together

Chapter Component Layout

Use data to build structures

Add different ICONS according to whether the session is locked or not

Do styling

// SRC/API /course

// Get the course chapter
export const getSectionAndLesson = courseId= > {
  return axios({
    method: 'get'.url: '/front/course/session/getSectionAndLesson'.params: {
      courseId
    }
  })
}
Copy the code
SRC /views/course-info/index.vue Use a separate package of course chapter components, use V-for to traverse all chapters, create multiple chapter components <template> <! -- cell, all content is distributed in a cell --> <van-cell-group> <! -- Top picture display area, <van-cell class="course-image"> <van-image width="375px" height="280px" fit="fill" : SRC =" info.courseimgURL "/> </van-cell> <! - course text messages - > < van - cell class = "info - the text" > < h2 > {{info. CourseName}} < / h2 > < p > {{info. PreviewFirstField}} < / p > < p Class ="info-text-buy"> <span class="l"> < I class="discounts">¥{{info.discounts}}</ I > < I Class = "price" > selections {{info. The price}} < / I > < / span > < span class = "r" > < I > {{info. Sales}} people already bought < / I > < I > every Wednesday and Friday update < / I > < / span > < / p > </van-cell> <! <van-cell class="tabs"> <! <van-tabs scrollSpy sticky> <! <van- TAB title=" van- TAB "> <! - bound data - > < div v - HTML = "info. CourseDescription" > < / div > < / van - TAB > <! - content - > < van - TAB class = "TAB - the content" title = "content" > <! <div v-for="section in SectionAndLesson" :key="section.id"> <div v-for="section in SectionAndLesson" :key="section.id"> Create a chapter component instance Pass chapter content to child components --> <lesson :section="section"></lesson> </div> </van-tab> </van-tabs> </van-cell> </van-cell-group> </template> <script> // import chapter component import Lesson from './son/lesson' // import interface: Import {getCourseById, getSectionAndLesson} from '@/ API /course' export default {name: 'course-info', // Receiving parameters: {// Path parameters courseId: {type: [Number, String], required: True}}, data () {return {// course details info: {}, // course chapter information data SectionAndLesson: // Create () {// call this.getcourseinfo ()}, methods: Async getCourseInfo () {// getCourseInfo () {// getCourseInfo () const {data} = await getCourseById(this.courseid) const {// getCourseById(this.courseid) data: Data2} = await getSectionAndLesson(this.courseId) // Give data this.info = data.content this.sectionAndLesson = Data2. Content. courseSectionList}}, / / registered components components: </script> <style lang=" SCSS "scoped> //. Van-hairline --top-bottom::after, // .van-cell::after { // display: none; //} // clear the cell component by default. Van-cell {padding: 0; } // top image. course-image {height: 280px; } // Middle text info. Info-text {padding: 10px 20px; .info-text-buy { display: flex; justify-content: space-between; } .discounts { margin-right: 5px; font-size: 24px; color: #ff7452; font-weight: 700; } .r { line-height: 28px; font-size: 12px; color: #666; i { padding: 7px 8px; border-radius: 5px; margin-left: 10px; background: #f8f9fa; font-weight: 700; }}}.tab-content {padding: 0 20px; } </style>Copy the code
SRC /views/course info/son/lesson. Vue <template> <div class="lesson"> <! <h2>{{section.sectionName}}</h2> <! <p class="lesson-item" v-for="item in section.courseLessons" :key="item.id"> <span>{{item.theme}}</span> <! <van-icon v-if="item.canPlay" size="20" name="play-circle" /> <van-icon v-else size="20" Name ="lock" /> </p> </div> </template> <script> export default {name: 'lesson', // get parameters: {section: {type: Object, required: true}}} </script> <style lang=" SCSS "scoped> // Single curriculum layout.lesson. justify-content: space-between; line-height: 20px; } </style>Copy the code

Bottom payment processing

The bottom payment function will only be displayed in unpurchased courses, purchased courses will not be displayed

Use the Vant TAB bar component, borrowing only the basic structure and writing the internal content yourself

Write content binding data directly

Finally, style processing, pay attention to the occlusion problem, conversion box model

SRC /views/course-info/index.vue Add a bottom TAB, modify the style <template> <div class="course-info"> <! -- cell, everything is distributed in a cell --> <! <van-cell-group :style="style"> <! -- Top picture display area, <van-cell class="course-image"> <van-image width="375px" height="280px" fit="fill" : SRC =" info.courseimgURL "/> </van-cell> <! - course text messages - > < van - cell class = "info - the text" > < h2 > {{info. CourseName}} < / h2 > < p > {{info. PreviewFirstField}} < / p > < p Class ="info-text-buy"> <span class="l"> < I class="discounts">¥{{info.discounts}}</ I > < I Class = "price" > selections {{info. The price}} < / I > < / span > < span class = "r" > < I > {{info. Sales}} people already bought < / I > < I > every Wednesday and Friday update < / I > < / span > < / p > </van-cell> <! <van-cell class="tabs"> <! <van-tabs scrollSpy sticky> <! <van- TAB title=" van- TAB "> <! - bound data - > < div v - HTML = "info. CourseDescription" > < / div > < / van - TAB > <! - content - > < van - TAB class = "TAB - the content" title = "content" > <! <div v-for="section in SectionAndLesson" :key="section.id"> <div v-for="section in SectionAndLesson" :key="section.id"> Create a chapter component instance Pass chapter content to child components --> <lesson :section="section"></lesson> </div> </van-tab> </van-tabs> </van-cell> </van-cell-group> <! --> <van-tabbar v-if="! Info. IsBuy "> < span class =" l "> < I > {{info. DiscountsTag}} < / I > < I class =" discounts "> selections {{info. Discounts}} < / I > < I Class = "price" > selections {{info. The price}} < / I > < / span > < van - button type = "primary" > buy now < / van - button > < / van - tabbar > < / div > < / template > <script> // Import section component import Lesson from './son/lesson' // import interface: Import {getCourseById, getSectionAndLesson} from '@/ API /course' export default {name: 'course-info', // Receiving parameters: {// Path parameters courseId: {type: [Number, String], required: True}}, data () {return {// course details info: {}, // course chapter information data SectionAndLesson: {}, // style binding, purchase setting location 0 style: {bottom: // Create () {this.getcourseinfo ()} // create () {this.getcourseinfo ()} Async getCourseInfo () {// getCourseInfo () const {data} = await getCourseById(this.courseid) Clear binding styles if (! Data.content.isbuy) this.style = null console.log(data) const {data: Data2} = await getSectionAndLesson(this.courseId) // Give data this.info = data.content this.sectionAndLesson = Data2. Content. courseSectionList}}, / / registered components components: {lesson}} </script> <style lang=" SCSS "scoped> top: 0; bottom: 50px; overflow-y: auto; } // Clear the cell component by default. Van-cell {padding: 0; } // top image. course-image {height: 280px; } // Middle text info. Info-text {padding: 10px 20px; .info-text-buy { display: flex; justify-content: space-between; } .discounts { margin-right: 5px; font-size: 24px; color: #ff7452; font-weight: 700; } .r { line-height: 28px; font-size: 12px; color: #666; i { padding: 7px 8px; border-radius: 5px; margin-left: 10px; background: #f8f9fa; font-weight: 700; }}}.tab-content {padding: 0 20px; } // bottom tabbar. Van-tabbar {display: flex; box-sizing: border-box; padding: 0 20px; justify-content: space-between; align-items: center; font-size: 14px; .l .discounts { margin: 0 5px; font-size: 24px; color: #ff7452; font-weight: 700; } .van-button { height: 80%; width: 50%; } } </style>Copy the code

Lesson video

Component to prepare

Encapsulate a component separately

Configure routes and use dynamic routes

When clicking the class, jump to the video playing page, bring the class ID (the premise is the unlocked course), and the teacher writes the jump video on the whole class

// SRC /router/index.js

{ //
    name: 'lesson-video'.path: '/lesson-video/:lessonId'.component: () = > import(/* webpackChunkName: 'lesson-video' */'@/views/course-info/lesson-video'),
    // Set path parameters
    props: true
  },
Copy the code
SRC /views/course-info/son/lesson. Vue -- Use V-for to traverse chapter periods, <p class="lesson-item" v-for="item in section.courseLessons" :key="item.id" @click="toVideo(item.canPlay, item.id)"> ......................... methods: {// Class click event, parameters are -canplay: whether to unlock, lessonId: 课时Id toVideo (canPlay, If (canPlay) return this.$router. Push ('/lessonVIDEO /${lessonId} ') {// If (canPlay) return this This.$toast(' this class is not locked ')}}Copy the code
<div class="lesson video"> {{lessonId}} </div> </template> <script> export default {name: 'lesson-video', // get props: {// Path parameter lessonId: {type: [Number, String], required: true } } } </script>Copy the code

Component structure

Use the navigation bar component at the top, return to the previous page function reserved

Use the interface to obtain aliyun video playing information according to ID and encapsulate the interface

Interface can return aliyun video ID and authorization

// SRC/API /course.js encapsulates the interface to get video playback information

// Obtain aliyun video playback information
export const videoPlayInfo = lessonId= > {
  return axios({
    method: 'get'.url: '/front/course/media/videoPlayInfo'.params: {
      lessonId
    }
  })
}
Copy the code
SRC /views/course-info/lesson-video.vue <template> <div class="lesson-video"> <! -- Top navigation bar, <van-nav-bar title=" video "left-text=" return" left-arrow @click-left="$router.go(-1)" /> </div> </template> Import {videoPlayInfo} from '@/ API /course' export default {name: LessonId: {type: [Number, String], required: This.getvideoinfo ()}, methods: Async getVideoInfo () {const {data} = await videoPlayInfo(this.lessonId) console.log(data)}} } </script>Copy the code

Aliyun video playback

The document address

Use h5 player

Use voD to play Playauth by playing credentials

It is recommended to use the online configuration feature to get the structure (which is more intuitive), but you can also use what it provides

Then use the generated code

Finally adjust the style

Public /index.html Add aliyun video playback required links<! DOCTYPEhtml>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <! Aliyun H5 play CSS file -->
    <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.9.3/skins/default/aliplayer-min.css" />
    <! -- Aliyun Bofangjs file -->
    <script type="text/javascript" charset="utf-8" src="https://g.alicdn.com/de/prismplayer/2.9.3/aliplayer-min.js"></script>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <! -- built files will be auto injected -->
  </body>
</html>
Copy the code
<template> <div class="lesson-video"> <! <van-nav-bar title=" video "left-text=" return" left-arrow @click-left="$router.go(-1)" /> <! <div class="prism-player" id="player"></div> </div> </template> <script> // /* eslint-disable*/ / import {videoPlayInfo} from '@/ API /course' export default {name: LessonId: {type: [Number, String], required: This.getvideoinfo ()}, methods: {// Get video playback information async getVideoInfo () {// call interface const {data} = await videoPlayInfo(this.lessonId) Create a new instance const player = new Aliplayer({// container ID ID: 'player', // The following two attributes are the video information returned by the above interface vid: data.content.fileId, playauth: data.content.playAuth, qualitySort: 'asc', format: 'mp4', mediaType: 'video', width: '100%', height: '500px', autoplay: true, isLive: false, rePlay: false, playsinline: true, preload: true, controlBarVisibility: 'hover', useH5Prism: true }, function (player) { console.log('The player is created') }) } } } </script>Copy the code

Check items on your phone

Premise: Cell phone and computer are on the same WIRELESS LAN

Obtain the computer’s wireless IP address

Mobile phone access computer IP address: port access