拉钩教育移动端项目实战 - 学习、课程详情、课时视频功能

307 阅读5分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

学习、课程详情、课时视频

学习功能

布局处理

使用公共底部导航栏

使用Vant的顶部导航栏组件

src/views/study/index.vue  添加顶部和底部导航栏

<template>
  <div class="study">
    <!-- 顶部标题栏 -->
    <van-nav-bar title="已购课程"/>
    <!-- 底部导航栏 -->
    <footBar></footBar>
  </div>
</template>

<script>
// 引入公共底部导航栏
import footBar from '@/common/foot-bar'
export default {
  name: 'study',
  // 注册组件
  components: {
    footBar
  }
}
</script>

公共组件处理

课程列表和选课功能是相同的,可以共用同一个组件,封装为公共组件

学习功能使用组件,调整部分样式

把选课下面的课程列表组件移动到公共组件牡蛎common中
src/views/course/index.vue  修改课程列表饮用地址

// 引入课程列表组件
import courseList from '@/common/course-list'
src/views/study/index.vue   学习页面使用移动后的课程列表组件,并调整样式

..............
<script>
..............
// 引入课程列表组件
import courseList from '@/common/course-list'
..............
</script>

<style lang="scss" scoped>
// 调整课程列表整体
.van-pull-refresh {
  top: 46px;
  bottom: 50px;
}
</style>

接口抽离

使用父组件向子组件传值的方式将接口传递给子组件,子组件使用即可

// src/api/course.js  封装获取已购课程接口

// 获取已购课程
export const getPurchaseCourse = () => {
  return axios({
    method: 'get',
    url: '/front/course/getPurchaseCourse'
  })
}
src/views/study/index.vue  学习界面引入接口并把接口传递给子组件

<template>
  <div class="study">
    ..........
    <!-- 创建课程列表组件实例 -->
    <courseList :getData="getData" />
    ..........
  </div>
</template>

<script>
// 引入接口:获取已购课程
import { getPurchaseCourse } from '@/api/course'
............
src/views/course/index.vue  选课界面同样把接口传递给子组件

<!-- 创建课程列表组件实例,使用参数传递的方法将要使用的接口传递给子组件 -->
<courseList :getData="getData" />

// 引入接口:获取广告位及对应广告 , 分页查询课程
import { getAllAds, getQueryCourses } from '@/api/course'
src/common/course-list.vue  课程列表组件拿到接口之后使用接口获取数据,注意不同接口返回数据名称不同,并且已购课程不分页,需要考虑到

<template>
  <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
    <!-- 列表组件 -->
    <van-list
      v-model="loading"
      :finished="finished"
      finished-text="没有更多了"
      @load="onLoad"
    >
      <!-- 列表项,使用for循环 -->
      <van-cell v-for="item in listData" :key="item.id">
        <!-- 左侧图片,因为两个接口返回数据名字不同,使用||判断取值 -->
        <img :src="item.courseImgUrl || item.image" alt="">
        <!-- 右侧信息 -->
        <div class="r">
          <h3>{{item.courseName || item.name}}</h3>
          <p class="FirstField">{{item.previewFirstField}}</p>
          <!-- 已购课程不返回也不需要展示价格,判断是否有价格字段即可 -->
          <p v-if="item.price">
            <span class="discounts">¥{{item.discounts}}</span>
            <!-- 直接使用删除线 -->
            <s>¥{{item.price}}</s>
          </p>
        </div>
      </van-cell>
    </van-list>
  </van-pull-refresh>
</template>

<script>
export default {
  name: 'courseList',
  // 接收参数
  props: {
    // 接收父组件传递过来的接口
    getData: {
      type: Function, // 类型为函数
      required: true // 必须
    }
  },
  data () {
    return {
      // 信号值 - 标记是否列表加载完毕
      finished: false,
      // 信号值 - 标记列表是否处于刷新状态
      loading: false,
      // 列表数据
      listData: [],
      // 课程查询页码
      currentPage: 1,
      // 信号值 - 下拉刷新
      refreshing: false
    }
  },
  methods: {
    // 下拉刷新函数
    async onRefresh () {
      // 重置页码为 1
      this.currentPage = 1
      // 判断接口名字,如果已购课程接口,列表依旧触底(否则如果数据较少会导致自动触发onLoad)
      this.finished = this.getData.name === 'getPurchaseCourse'
      // 调用父组件传递过来的接口获取数据
      const { data } = await this.getData({
        currentPage: this.currentPage,
        pageSize: 10,
        status: 1
      })
      // 根据请求结果判断当前显示的页面
      if (data.code === '000000' && data.data.records !== 0) {
        // 获取全部课程成功执行
        // 直接替换数据
        this.listData = data.data.records
        // 页数 +1
        this.currentPage++
        // 修改下拉刷新信号值
        this.refreshing = false
        // 弹出提示
        this.$toast('刷新成功')
      } else if (data.content) {
        // 获取已购课程成功
        // 直接替换数据
        this.listData = data.content
        // 页数 +1
        this.currentPage++
        // 修改下拉刷新信号值
        this.refreshing = false
        // 弹出提示
        this.$toast('刷新成功')
      }
    },
    // 列表触底刷新事件,第一次加载的时候就会默认执行一次
    async onLoad () {
      // 调用父组件传递过来的接口获取数据
      const { data } = await this.getData({
        currentPage: this.currentPage,
        pageSize: 10,
        status: 1
      })
      // 根据请求结果判断当前显示的页面
      if (data.code === '000000' && data.data.records !== 0) {
        // 获取课程列表成功
        // 向列表添加数据
        this.listData.push(...data.data.records)
        // 页数 +1
        this.currentPage++
        if (this.listData.length >= data.data.total) {
          // 如果列表长度大于等于总条数,设置触底
          this.finished = true
        }
      } else if (data.content) {
        // 获取已购课程成功
        // 向列表添加数据(其实也可以直接替换,因为这个接口返回的数据不分页)
        this.listData.push(...data.content)
        // 页数 +1
        this.currentPage++
        // 直接设置触底
        this.finished = true
      }
      // 无论如何都要设置加载状态结束
      this.loading = false
    }
  }
}
</script>

<style lang="scss" scoped>
// 列表整体定位
.van-pull-refresh {
  position: fixed;
  left: 0;
  right: 0;
  overflow-y: auto; // 开启Y轴滚动条
  // 单个课程
  .van-cell .van-cell__value {
    display: flex;
    // 清除默认效果
    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>

封装接口与数据绑定

使用获取已购课程接口

绑定数据即可,但接口返回数据名字不同

使用数据时进行||判断,分情况获取不同数据,价格不需要加载了,使用v-if判断是否有价格数据

上面已经搞定了

课程详情

选课页面和学习页面都支持跳转详情页

组件准备

课程详情为单独组件,不需要登录即可访问

课程详情需要根据课程id不同展示不同内容,需要使用动态路由参数和路径传参

点击列表项的时候跳转到详情页面,并带着课程id - params

// src/router/index.js  添加新路由 - 课程详情

{ // 课程详情
    name: 'course-info',
    path: '/course-info/:courseId',
    component: () => import(/* webpackChunkName: 'course-info' */'@/views/course-info'),
    // 设置路径参数
    props: true
}
src/common/course-list.vue  课程列表设置点击事件,跳转到课程详情页,并且带着课程id

<!-- 列表项,使用for循环 ,添加点击事件跳转到课程详情页-->
<van-cell v-for="item in listData" :key="item.id" @click="$router.push(`/course-info/${item.id}`)">
src/views/course-info/index.vue  新建课程详情目录和文件

<template>
  <div>{{ courseId }}</div>
</template>

<script>
export default {
  name: 'course-info',
  // 接收参数
  props: {
    // 路径参数
    courseId: {
      type: [Number, String],
      required: true
    }
  }
}
</script>

接口封装

使用获取课程详情接口,在页面初始化的时候获取数据

将获取到的数据存储起来以便后面使用

// src/api/course.js  封装获取课程详情接口
// 获取课程详情
export const getCourseById = courseId => {
  return axios({
    method: 'get',
    url: '/front/course/getCourseById',
    params: {
      courseId
    }
  })
}
src/views/course-info/index.vue  初始化的时候利用路径参数获取课程详情

<template>
  <div>{{ courseId }}</div>
</template>

<script>
// 引入接口:获取课程详情
import { getCourseById } from '@/api/course'
export default {
  name: 'course-info',
  // 接收参数
  props: {
    // 路径参数
    courseId: {
      type: [Number, String],
      required: true
    }
  },
  data () {
    return {
      // 课程详情
      info: {}
    }
  },
  // 钩子函数
  created () {
    // 调用函数
    this.getCourseInfo()
  },
  methods: {
    // 初始化获取课程详情
    async getCourseInfo () {
      const { data } = await getCourseById(this.courseId)
      console.log(data)
    }
  }
}
</script>

主题内容区域 - 顶部

可以使用之前用过的Vant单元格组件

添加结构绑定数据,进行样式处理

src/views/course-info/index.vue  设定顶部图片,中间文字信息,调整样式

<template>
  <!-- 单元格,所有内容全部分布在一个个单元格中 -->
  <van-cell-group>
    <!-- 顶部图片展示区,绑定数据 -->
    <van-cell class="course-image">
      <van-image
        width="375px"
        height="280px"
        fit="fill"
        :src="info.courseImgUrl"
      />
    </van-cell>
    <!-- 课程文字信息 -->
    <van-cell class="info-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">¥{{info.price}}</i>
        </span>
        <span class="r">
          <i>{{info.sales}}人已购</i>
          <i>每周三、周五更新</i>
        </span>
      </p>
    </van-cell>
  </van-cell-group>
</template>

<script>
// 引入接口:获取课程详情
import { getCourseById } from '@/api/course'
export default {
  name: 'course-info',
  // 接收参数
  props: {
    // 路径参数
    courseId: {
      type: [Number, String],
      required: true
    }
  },
  data () {
    return {
      // 课程详情
      info: {}
    }
  },
  // 钩子函数
  created () {
    // 调用函数
    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;
}
// 清除单元格组件默认padding
.van-cell {
  padding: 0;
}
// 顶部图片
.course-image {
  height: 280px;
}
// 中间文字信息
.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>

选项卡处理

使用Vant的标签页组件,开启粘性布局会在切换后自动吸附到顶部,使用滚动导航属性

添加结构绑定数据,处理样式

课程详情信息后台是通过富文本编辑器保存的,所以直接使用v-html设置即可

src/views/course-info/index.vue  添加课程详情和内容,先设置课程详情

<!-- 底部课程详情、内容 -->
    <van-cell class="tabs">
      <!-- tab标签,开启粘性布局、滚动 -->
      <van-tabs scrollspy sticky>
        <!-- 详情 -->
        <van-tab title="详情">
          <!-- 绑定数据 -->
          <div v-html="info.courseDescription"></div>
        </van-tab>
        <!-- 内容 -->
        <van-tab title="内容">123</van-tab>
      </van-tabs>
    </van-cell>
</van-cell-group>

章节组件封装

使用获取课程章节接口

单独封装一个章节展示组件,单独章节封装为一个组件,包括章节名、课时

组件内部使用v-for创建结构

下面一起写

章节组件布局

使用数据搭建结构

根据课时是否锁定添加不同的图标

进行样式处理

// src/api/course.js  封装获取课程章节接口

// 获取课程章节
export const getSectionAndLesson = courseId => {
  return axios({
    method: 'get',
    url: '/front/course/session/getSectionAndLesson',
    params: {
      courseId
    }
  })
}
src/views/course-info/index.vue  课程内容使用单独封装的课程章节组件,使用v-for遍历全部章节,创建多个章节组件

<template>
  <!-- 单元格,所有内容全部分布在一个个单元格中 -->
  <van-cell-group>
    <!-- 顶部图片展示区,绑定数据 -->
    <van-cell class="course-image">
      <van-image
        width="375px"
        height="280px"
        fit="fill"
        :src="info.courseImgUrl"
      />
    </van-cell>
    <!-- 课程文字信息 -->
    <van-cell class="info-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">¥{{info.price}}</i>
        </span>
        <span class="r">
          <i>{{info.sales}}人已购</i>
          <i>每周三、周五更新</i>
        </span>
      </p>
    </van-cell>
    <!-- 底部课程详情、内容 -->
    <van-cell class="tabs">
      <!-- tab标签,开启粘性布局、滚动 -->
      <van-tabs scrollspy sticky>
        <!-- 详情 -->
        <van-tab title="详情">
          <!-- 绑定数据 -->
          <div v-html="info.courseDescription"></div>
        </van-tab>
        <!-- 内容 -->
        <van-tab class="tab-content" title="内容">
          <!-- 遍历课程章节 -->
          <div v-for="section in SectionAndLesson" :key="section.id">
            <!-- 创建章节组件实例,把章节内容 传递给子组件 -->
            <lesson :section="section"></lesson>
          </div>
        </van-tab>
      </van-tabs>
    </van-cell>
  </van-cell-group>
</template>

<script>
// 引入章节组件
import lesson from './son/lesson'
// 引入接口:获取课程详情,获取课程章节
import { getCourseById, getSectionAndLesson } from '@/api/course'
export default {
  name: 'course-info',
  // 接收参数
  props: {
    // 路径参数
    courseId: {
      type: [Number, String],
      required: true
    }
  },
  data () {
    return {
      // 课程详情
      info: {},
      // 课程章节信息数据
      SectionAndLesson: {}
    }
  },
  // 钩子函数
  created () {
    // 调用函数
    this.getCourseInfo()
  },
  methods: {
    // 初始化获取课程详情
    async getCourseInfo () {
      // 获取课程详情
      const { data } = await getCourseById(this.courseId)
      // 获取课程章节
      const { data: data2 } = await getSectionAndLesson(this.courseId)
      // 把获取到的课程详情数据交给data
      this.info = data.content
      this.SectionAndLesson = data2.content.courseSectionList
    }
  },
  // 注册组件
  components: {
    lesson
  }
}
</script>

<style lang="scss" scoped>
// 去掉默认的伪造元素
// .van-hairline--top-bottom::after,
// .van-cell::after {
//   display: none;
// }
// 清除单元格组件默认padding
.van-cell {
  padding: 0;
}
// 顶部图片
.course-image {
  height: 280px;
}
// 中间文字信息
.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>

src/views/course-info/son/lesson.vue  单独封装一个课程章节子组件

<template>
  <div class="lesson">
    <!-- 章节标题 -->
    <h2>{{section.sectionName}}</h2>
    <!-- 使用v-for遍历章节课时 -->
    <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',
  // 获取参数
  props: {
    section: {
      type: Object,
      required: true
    }
  }
}
</script>

<style lang="scss" scoped>
// 单个课程左右布局
.lesson .lesson-item {
  display: flex;
  justify-content: space-between;
  line-height: 20px;
}
</style>

底部支付处理

底部支付功能在未购买的课程中才会展示,已购买不展示

使用Vant标签栏组件,只借用基本结构,内部内容自己书写

直接书写内容绑定数据即可

最后进行样式处理,注意遮挡问题,转换盒模型

src/views/course-info/index.vue  添加底部标签栏,调整样式

<template>
  <div class="course-info">
    <!-- 单元格,所有内容全部分布在一个个单元格中 -->
    <!-- 样式绑定,实现购买和不购买不同样式 -->
    <van-cell-group :style="style">
      <!-- 顶部图片展示区,绑定数据 -->
      <van-cell class="course-image">
        <van-image
          width="375px"
          height="280px"
          fit="fill"
          :src="info.courseImgUrl"
        />
      </van-cell>
      <!-- 课程文字信息 -->
      <van-cell class="info-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">¥{{info.price}}</i>
          </span>
          <span class="r">
            <i>{{info.sales}}人已购</i>
            <i>每周三、周五更新</i>
          </span>
        </p>
      </van-cell>
      <!-- 底部课程详情、内容 -->
      <van-cell class="tabs">
        <!-- tab标签,开启粘性布局、滚动 -->
        <van-tabs scrollspy sticky>
          <!-- 详情 -->
          <van-tab title="详情">
            <!-- 绑定数据 -->
            <div v-html="info.courseDescription"></div>
          </van-tab>
          <!-- 内容 -->
          <van-tab class="tab-content" title="内容">
            <!-- 遍历课程章节 -->
            <div v-for="section in SectionAndLesson" :key="section.id">
              <!-- 创建章节组件实例,把章节内容 传递给子组件 -->
              <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">¥{{info.discounts}}</i>
        <i class="price">¥{{info.price}}</i>
      </span>
      <van-button type="primary">立即购买</van-button>
    </van-tabbar>
  </div>
</template>

<script>
// 引入章节组件
import lesson from './son/lesson'
// 引入接口:获取课程详情,获取课程章节
import { getCourseById, getSectionAndLesson } from '@/api/course'
export default {
  name: 'course-info',
  // 接收参数
  props: {
    // 路径参数
    courseId: {
      type: [Number, String],
      required: true
    }
  },
  data () {
    return {
      // 课程详情
      info: {},
      // 课程章节信息数据
      SectionAndLesson: {},
      // 样式绑定,购买设置定位为0
      style: {
        bottom: 0
      }
    }
  },
  // 钩子函数
  created () {
    // 调用函数
    this.getCourseInfo()
  },
  methods: {
    // 初始化获取课程详情
    async getCourseInfo () {
      // 获取课程详情
      const { data } = await getCourseById(this.courseId)
      // 如果没有购买,将绑定样式清空
      if (!data.content.isBuy) this.style = null
      console.log(data)
      // 获取课程章节
      const { data: data2 } = await getSectionAndLesson(this.courseId)
      // 把获取到的课程详情数据交给data
      this.info = data.content
      this.SectionAndLesson = data2.content.courseSectionList
    }
  },
  // 注册组件
  components: {
    lesson
  }
}
</script>

<style lang="scss" scoped>
// 内容区定位
.van-cell-group {
  position: fixed;
  top: 0;
  bottom: 50px;
  overflow-y: auto;
}
// 清除单元格组件默认padding
.van-cell {
  padding: 0;
}
// 顶部图片
.course-image {
  height: 280px;
}
// 中间文字信息
.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;
}
// 底部标签栏
.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>

课时视频

组件准备

单独封装一个组件

设置路由,使用动态路由

在点击课时的时候跳转视频播放页,带上课时id(前提是当前解锁的课程),老师把跳转视频写在了整个课时上

// src/router/index.js  添加课时视频路由

{ // 课时视频
    name: 'lesson-video',
    path: '/lesson-video/:lessonId',
    component: () => import(/* webpackChunkName: 'lesson-video' */'@/views/course-info/lesson-video'),
    // 设置路径参数
    props: true
  },
src/views/course-info/son/lesson.vue  给每一个课时添加点击事件

<!-- 使用v-for遍历章节课时,并给每一个课时添加点击事件-->
<p class="lesson-item" v-for="item in section.courseLessons" :key="item.id" @click="toVideo(item.canPlay, item.id)">
.........................
methods: {
  // 课时点击事件,参数分别为 - canPlay:是否解锁,lessonId:课时Id
  toVideo (canPlay, lessonId) {
    // 如果解锁直接带着课时Id跳转
    if (canPlay) return this.$router.push(`/lesson-video/${lessonId}`)
    // 未解锁弹出提示
    return this.$toast('该课时课程未解锁')
  }
}
src/views/course-info/lesson-video.vue  新建课时视频组件

<template>
  <div class="lesson-video">
    {{lessonId}}
  </div>
</template>

<script>
export default {
  name: 'lesson-video',
  // 获取参数
  props: {
    // 路径参数
    lessonId: {
      type: [Number, String],
      required: true
    }
  }
}
</script>

组件结构

顶部使用导航栏组件即可,返回上一页功能保留

使用根据id获取阿里云视频播放信息接口,封装接口

接口可以返回阿里云视频id和授权

// src/api/course.js  封装获取视频播放信息接口

// 获取阿里云视频播放信息
export const videoPlayInfo = lessonId => {
  return axios({
    method: 'get',
    url: '/front/course/media/videoPlayInfo',
    params: {
      lessonId
    }
  })
}
src/views/course-info/lesson-video.vue  添加导航栏在初始化后获得视频播放信息

<template>
  <div class="lesson-video">
    <!-- 顶部导航栏,添加点击返回事件 -->
    <van-nav-bar
      title="视频"
      left-text="返回"
      left-arrow
      @click-left="$router.go(-1)"
    />
  </div>
</template>

<script>
// 引入接口:获取视频播放信息
import { videoPlayInfo } from '@/api/course'
export default {
  name: 'lesson-video',
  // 获取参数
  props: {
    // 路径参数
    lessonId: {
      type: [Number, String],
      required: true
    }
  },
  // 钩子函数
  created () {
    // 获取视频播放信息
    this.getVideoInfo()
  },
  methods: {
    // 获取视频播放信息
    async getVideoInfo () {
      // 调用接口
      const { data } = await videoPlayInfo(this.lessonId)
      console.log(data)
    }
  }
}
</script>

阿里云视频播放

文档地址

使用h5播放器

使用视频点播通过播放凭证播放playauth

推荐使用在线配置功能获取结构(更直观),当然也可以使用它提供的

然后使用生成的代码

最后调整样式

public/index.html  添加阿里云视频播放需要的链接

<!DOCTYPE html>
<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">
    <!-- 阿里云H5播放css文件 -->
    <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.9.3/skins/default/aliplayer-min.css" />
    <!-- 阿里云bofangjs文件 -->
    <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>
src/views/course-info/lesson-video.vue  添加阿里云播放器

<template>
  <div class="lesson-video">
    <!-- 顶部导航栏,添加点击返回事件 -->
    <van-nav-bar
      title="视频"
      left-text="返回"
      left-arrow
      @click-left="$router.go(-1)"
    />
    <!-- 视频播放容器,设置id -->
    <div class="prism-player" id="player"></div>
  </div>
</template>
<script>
// 禁止eslint检查下面代码,不设置可能会报错,我就报错了
/* eslint-disable*/
// 引入接口:获取视频播放信息
import { videoPlayInfo } from '@/api/course'
export default {
  name: 'lesson-video',
  // 获取参数
  props: {
    // 路径参数
    lessonId: {
      type: [Number, String],
      required: true
    }
  },
  // 钩子函数
  created () {
    // 获取视频播放信息
    this.getVideoInfo()
  },
  methods: {
    // 获取视频播放信息
    async getVideoInfo () {
      // 调用接口
      const { data } = await videoPlayInfo(this.lessonId)
      // 设置阿里云视频播放器,新建一个实例
      const player = new Aliplayer({
        // 容器ID
        id: 'player',
        // 下面两个属性是上面接口返回的视频信息
        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>

通过手机查看项目

前提:手机和电脑在一个无线局域网内

获取电脑的无线网ip地址

手机访问电脑IP地址:端口即可访问