项目实训:虚拟现实环境下的远程教育和智能评估系统(十三)

一、课程学习和课程详情展示

<template>
  <div class="course_detail">
    <y-study v-if="courseInfo.allowStudy" ref="watchVideo" :course-info="courseInfo" :play-period="playPeriod" @playfunc="videoPlay" />
    <y-detail v-else ref="watchVideo" :course-info="courseInfo" :teacher-info="teacherInfo" />
    <div class=" detail_info detail_box clearfix">
      <div class="layout_left">
        <ul class="course_tab clearfix">
          <li :class="{on: tab == 'info'}"><a href="javascript:" @click="tab = 'info'">课程介绍</a></li>
          <li :class="{on: tab == 'big'}"><a href="javascript:" @click="tab = 'big'">课程目录</a></li>
          <li :class="{on: tab == 'comment'}"><a href="javascript:" @click="tab = 'comment'">课程评论</a></li>
        </ul>
        <div v-if="tab == 'info'" class="content_info">
          <div class="introduce" v-html="courseInfo.introduce" />
        </div>
        <div v-if="tab == 'big'" class="content_info">
          <y-catalog :list="courseInfo.chapterRespList" :play-period="playPeriod" @playfunc="videoPlay" />
        </div>
        <div v-if="tab == 'comment'" class="content_info">
          <y-comment :id="courseInfo.id" />
        </div>
      </div>
      <div class="layout_right">
        <div class="teacher_info clearfix">
          <span class="head">讲师简介</span>
          <div class="teacher_msg">
            <div v-if="teacherInfo" class="teacher_msg_right">
              <img v-if="teacherInfo.lecturerHead" class="teacher_phone" :src="teacherInfo.lecturerHead" alt="">
              <img v-else class="teacher_phone" src="~/assets/image/friend.jpg" alt="">
              <div class="teacher_name">{{ teacherInfo.lecturerName }}</div>
              <div class="teacher_position">{{ teacherInfo.lecturerPosition }}</div>
              <div class="info_box" v-html="teacherInfo.introduce" />
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- <bottom /> -->
  </div>
</template>
<script>
import YDetail from '@/components/course/Detail'
import YCatalog from '@/components/course/Catalog'
import YStudy from '@/components/course/Study'
import YComment from '@/components/course/Comment'
import cookies from '@/utils/cookies'
import { courseDetail, playSign, studyProgress, userCourseDetail } from '~/api/course.js'

export default {
  components: {
    YDetail,
    YCatalog,
    YStudy,
    YComment
  },
  async asyncData(context) {
    try {
      const result = {}
      const token = cookies.getInServerToken(context.req)
      if (token) {
        // 登录
        const courseData = await userCourseDetail({ courseId: context.params.id }, token)
        result.courseInfo = courseData
        if (courseData.lecturerResp) {
          result.teacherInfo = courseData.lecturerResp
        }
      } else {
        // 没登录
        const courseData = await courseDetail({ courseId: context.params.id })
        result.courseInfo = courseData
        if (courseData.lecturerResp) {
          result.teacherInfo = courseData.lecturerResp
        }
      }
      return result
    } catch (e) {
      context.error({ message: 'Data not found', statusCode: 404 })
    }
  },
  data() {
    return {
      tab: 'info',
      playPeriod: '', // 当前播放课时
      userStudy: {} // 进度
    }
  },
  head() {
    return {
      title: this.courseInfo.courseName + '-' + this.$store.state.websiteInfo.websiteName
    }
  },
  mounted() {
    if (this.courseInfo.allowStudy) {
      this.tab = 'big'
    }

    if (this.courseInfo.allowStudy) {
      // 可以播放,自动获取最后学习的课程
      playSign({ courseId: this.courseInfo.id, clientIp: '172.0.0.1' }).then(res => {
        this.playPeriod = res.periodId
        if (res.periodId) {
          this.play(res, false)
        }
      })
    }

    window.s2j_onVideoPause = (vid) => {
      // 暂停
      clearInterval(this.progressInterval)
    }

    window.s2j_onVideoPlay = (vid) => {
      // 播放
      this.progressInterval = setInterval(() => {
        this.studyRecord()
      }, 3000)
    }

    window.s2j_onPlayOver = (vid) => {
      // 播放完成
      clearInterval(this.progressInterval)
      this.studyRecord()
    }
  },
  methods: {
    videoPlay(periodId) {
      if (this.courseInfo.allowStudy) {
        window.scrollTo(0, 0)
        playSign({ periodId: periodId, courseId: this.courseInfo.id, clientIp: '172.0.0.1' }).then(res => {
          this.playPeriod = res.periodId
          if (res.periodId) {
            this.play(res, true)
          }
        })
      } else {
        this.$msgBox({
          content: '购买后才可以学习',
          isShowCancelBtn: false
        })
        return false
      }
    },
    play(data, autoplay) {
      const box = this.$refs.watchVideo.$refs.videobox
      this.player?.destroy()
      const param = {
        'width': box.offsetWidth,
        'height': box.offsetHeight,
        'forceH5': true,
        'watchStartTime': data.startTime,
        autoplay: autoplay
      }
      if (data.vodPlatform === 2) {
        const vodPlayConfig = JSON.parse(data.vodPlayConfig)
        param.code = vodPlayConfig.code
        param.playsafe = vodPlayConfig.token
        param.ts = vodPlayConfig.ts
        param.sign = vodPlayConfig.sign
        param.vid = vodPlayConfig.vid
      } else {
        const vodPlayConfig = JSON.parse(data.vodPlayConfig)
        if (vodPlayConfig.hdUrl) {
          param.url = vodPlayConfig.hdUrl
        } else {
          param.url = vodPlayConfig.sdUrl
        }
      }
      this.player = window.polyvObject('#player').videoPlayer(param)

      this.userStudy.studyId = data.studyId
      this.userStudy.resourceId = data.resourceId
    },
    // 记录进度
    studyRecord() {
      // this.userStudy.totalDuration = this.getDuration()
      this.userStudy.currentDuration = this.getCurrentTime()
      studyProgress(this.userStudy).then(res => {
        if (res === 'OK') {
          // 完成,暂停同步
          clearInterval(this.progressInterval)
        }
      })
    },
    // 获取视频当前播放时长
    getCurrentTime() {
      if (this.player && this.player.j2s_getCurrentTime) {
        return this.player.j2s_getCurrentTime()
      }
      return 0
    },
    // 获取视频总时长
    getDuration() {
      if (this.player && this.player.j2s_getDuration) {
        return this.player.j2s_getDuration()
      }
      return 0
    }
  }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.course_detail {
  .detail_info {
    margin: 20px auto 60px;
    width: 1200px;
  }

  .layout_left {
    width: 920px;
    float: left;

    .info_body {
      margin-bottom: 30px;
    }
  }

  .course_tab {
    padding-left: 30px;
    background: #fff;
    border-radius: 8px;
    margin-bottom: 20px;

    li {
      float: left;
      height: 66px;
      line-height: 66px;
      margin-right: 80px;

      &.on {
        border-bottom: 2px solid #D51423;

        a {
          color: #D51423;
        }
      }

      a {
        color: #000;
        font-size: 14px;
        font-weight: 700;

        &:hover {
          text-decoration: none;
          color: #D51423;
        }
      }
    }
  }

  .content_info {
    padding: 30px;
    background: #fff;
    border-radius: 8px;
    min-height: 400px;

    .title {
      border-left: 3px solid #000;
      padding-left: 12px;
      font-size: 16px;
      color: #000;
      font-weight: 700;
      margin-bottom: 25px;
    }

    .introduce {
      font-size: 14px;
      line-height: 30px;
      color: #333;
      padding-left: 8px;
    }
  }

  .layout_right {
    width: 260px;
    float: right;
  }

  .teacher_info {
    background: #fff;
    border-radius: 8px;
    position: relative;
    min-height: 180px;

    .head {
      display: block;
      line-height: 60px;
      height: 60px;
      padding-left: 20px;
      font-size: 14px;
      color: #333;
      border-bottom: 1px solid rgb(228, 228, 228);
    }

    .teacher_phone {
      width: 50px;
      height: 50px;
      border-radius: 50%;
      background: rgb(228, 228, 228);
      text-align: center;
      line-height: 46px;
      font-size: 13px;
      color: #999;
      float: left;
      margin: 0px 10px 0 10px;

      img {
        width: 46px;
        height: 46px;
        border-radius: 50%;
      }
    }

    .teacher_msg_right {
      width: 235px;
      float: right;
      margin: 12px 15px 12px 12px;
      line-height: 20px;
    }

    .teacher_name {
      font-size: 18px;
      font-weight: 700;
      color: #333;
      margin: 5px 0;
    }

    .teacher_position {
      font-size: 12px;
      color: #333;
      margin-bottom: 5px;
    }
  }
}
</style>

二、用户登录的前端界面

<template>
  <div class="login_page">
    <y-header :hide-top="true" :hide-search="true" />
    <div class="login_body">
      <div class="login_box">
        <div class="login_logo"><img src="../assets/image/login_bg.png"></div>
        <div class="center_box">
          <div v-if="!(userInfo)" :class="{login_form: true, rotate: tab == 2}">
            <div v-if="tabp == 1" style="height: 30px" />
            <div :class="{tabs: true, clearfix: true, r180: tabp == 2}">
              <div class="fl tab" @click="changetab(1)">
                <span :class="{on: tab == 1}">登录</span>
              </div>
              <div class="fl tab" @click="changetab(2)">
                <span :class="{on: tab == 2}">注册</span>
              </div>
            </div>
            <div v-if="tabp == 1" class="form_body">
              <form action="" @submit="loginSubmit">
                <input v-model="loginObj.mobile" type="text" placeholder="请输入手机号或邮箱">
                <div class="error_msg">{{ errTip1 }}</div>
                <input v-model="loginObj.password" type="password" placeholder="6-20位密码,可用数字/字母/符号组合">
                <div class="error_msg">{{ errTip2 }}</div>
                <input v-if="subState" type="submit" disabled="disabled" value="登录中···" class="btn">
                <input v-else type="submit" value="登录" class="btn">
              </form>
              <nuxt-link class="is_go" :to="{name: 'reset'}">忘记密码</nuxt-link>
              <div style="margin-top: 20px;">注册即可体验</div>
            </div>
            <div v-if="tabp == 2" class="form_body r180">
              <form action="" @submit="regSubmit">
                <input v-model="registerObj.mobile" type="text" placeholder="请输入手机号">
                <div class="phone_yzm">
                  <input v-model="registerObj.code" type="text" name="code" placeholder="请输入手机验证码" class="phone" maxlength="6">
                  <y-button :mobile="registerObj.mobile" />
                </div>
                <input v-model="registerObj.mobilePwd" type="password" placeholder="6-20位密码,可用数字/字母/符号组合">
                <input v-model="registerObj.repassword" type="password" placeholder="确认密码">
                <div class="mgt20 font_14">
                  <input id="tonyi" v-model="registerObj.check" type="checkbox">
                  <label for="tonyi">我已经阅读并同意</label><a href="jvascript:" class="c_blue" @click="xieyi = true">《用户协议》</a>
                </div>
                <input v-if="subState" type="submit" disabled="disabled" value="提交中···" class="btn">
                <input v-else type="submit" value="注册" class="btn">
              </form>
            </div>
          </div>
          <div v-else class="login_form">
            <div style="height: 60px" />
            <div class="login_title is_login">领课教育系统(开源版)</div>
            <div class="form_body">
              <div class="img_box">
                <img v-if="userInfo.userHead" :src="userInfo.userHead" alt="">
                <img v-else src="../assets/image/friend.jpg" alt="">
              </div>
              <ul class="btn_box clearfix">
                <li>
                  <nuxt-link :to="{name: 'account-course'}">我的课程</nuxt-link>
                </li>
                <li>
                  <nuxt-link :to="{name: 'account-order'}">我的订单</nuxt-link>
                </li>
              </ul>
              <div>
                <a href="javascript:" class="out_btn" @click="signOut">退出登录</a>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div v-if="xieyi" class="xieyi" @click.self="xieyi = false">
      <div class="xieyi_content">
        <div class="xieyi_title">用户协议</div>
        <div v-if="websiteInfo && websiteInfo.websiteUserAgreement" class="xieyi_body" v-html="websiteInfo.websiteUserAgreement" />
        <input type="button" class="xieyi_btn" value="确定" @click="xieyi = false">
      </div>
    </div>
    <!-- <y-bottom /> -->
  </div>
</template>
<script>
import YHeader from '~/components/common/Header'
import YButton from '@/components/common/Code'
import { getOsInfo, getBrowserInfo } from '@/utils/utils'
import { register, userLogin } from '@/api/login.js'

export default {
  components: {
    YHeader,
    YButton
  },
  data() {
    return {
      tab: this.$route.query.tab || 1,
      tabp: this.$route.query.tab || 1,
      websiteInfo: this.$store.state.websiteInfo,
      userInfo: this.$store.state.userInfo,
      subState: false, // 提交状态
      xieyi: false, // 用户协议
      errTip1: '',
      errTip2: '',
      ipInfo: {},
      loginObj: {
        mobile: '',
        password: ''
      },
      registerObj: {
        mobile: '',
        code: '',
        mobilePwd: '',
        repassword: '',
        check: false
      }
    }
  },
  head() {
    return {
      title: '用户登录-' + this.websiteInfo.websiteName,
      meta: [
        {
          hid: 'keywords',
          name: 'keywords',
          content: this.websiteInfo.websiteName
        },
        {
          hid: 'description',
          name: 'description',
          content: this.websiteInfo.websiteDesc
        }
      ]
    }
  },
  mounted() {
    if (this.$route.query.t === 'login') {
      this.$store.commit('SIGN_OUT')
      this.userInfo = ''
      this.errTip1 = '未登录或登录超时,请重新登录'
    } else if (this.$route.query.t) {
      window.location.href = '/index'
    }
  },
  methods: {
    signOut() {
      // this.SIGN_OUT();
      this.$store.commit('SIGN_OUT')
      this.userInfo = ''
    },
    changetab(int) {
      this.tab = int
      const _that = this
      setTimeout(function() {
        _that.tabp = int
      }, 200)
    },
    loginSubmit(e) {
      e.preventDefault()
      if (this.subState) {
        return false
      }
      this.errTip1 = ''
      this.errTip2 = ''
      if (!(/^1\d{10}$/.test(this.loginObj.mobile.trim())) && !(/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/).test(this.loginObj.mobile.trim())) {
        this.errTip1 = '请输入正确手机号或者邮箱'
        return false
      }
      if (this.loginObj.password.length < 6) {
        this.errTip2 = '请输入正确的账号或密码'
        return false
      }
      this.loginObj.loginIp = this.ipInfo.ip
      this.loginObj.province = this.ipInfo.pro
      this.loginObj.city = this.ipInfo.city
      this.loginObj.os = getOsInfo().version
      this.loginObj.browser = getBrowserInfo().name + getBrowserInfo().version
      this.$nuxt.$loading.start()
      this.subState = true
      userLogin(this.loginObj).then(res => {
        this.subState = false
        this.$nuxt.$loading.finish()
        this.$store.commit('SET_TOKEN', res.token)
        this.$store.commit('GET_TEMPORARYURL')
        this.$store.dispatch('GET_USERINFO', store => {
          this.userInfo = this.$store.state.userInfo
          window.location.href = this.$store.state.temporaryUrl
        })
      }).catch((error) => {
        this.subState = false
        this.$nuxt.$loading.finish()
        this.$msgBox({
          content: error.msg,
          isShowCancelBtn: false
        })
      })
      return false
    },
    // 注册
    regSubmit: function(e) {
      e.preventDefault()
      if (this.subState) {
        return false
      }
      if (!(/^1\d{10}$/.test(this.registerObj.mobile.trim())) || this.registerObj.mobile.trim().length !== 11) {
        this.$msgBox({
          content: '请输入正确手机',
          isShowCancelBtn: false
        }).catch(() => {
        })
        return false
      }
      if (!this.registerObj.code || this.registerObj.code.length !== 6) {
        this.$msgBox({
          content: '请输入正确的手机验证码',
          isShowCancelBtn: false
        }).catch(() => {
        })
        return false
      }
      if (this.registerObj.mobilePwd.length < 6 || this.registerObj.mobilePwd.length > 16) {
        this.$msgBox({
          content: '请输入6-16位的登录密码,区分大小写,不可有空格',
          isShowCancelBtn: false
        }).catch(() => {
        })
        return false
      }
      if (this.registerObj.mobilePwd !== this.registerObj.repassword) {
        this.$msgBox({
          content: '两次输入密码不一致',
          isShowCancelBtn: false
        }).catch(() => {
        })
        return false
      }
      if (!this.registerObj.check) {
        this.$msgBox({
          content: '请先阅读并同意用户协议',
          isShowCancelBtn: false
        }).catch(() => {
        })
        return false
      }
      this.$nuxt.$loading.start()
      this.subState = true
      this.registerObj.loginIp = this.ipInfo.ip
      this.registerObj.province = this.ipInfo.pro
      this.registerObj.city = this.ipInfo.city
      this.registerObj.os = getOsInfo().version
      this.registerObj.browser = getBrowserInfo().name + getBrowserInfo().version
      register(this.registerObj).then(res => {
        this.$nuxt.$loading.finish()
        this.subState = false
        this.$msgBox({
          content: '注册成功!',
          confirmBtnText: '立即登录',
          isShowCancelBtn: false
        }).then(() => {
          this.changetab(1)
        }).catch(() => {
          this.changetab(1)
        })
        this.registerObj = {
          mobile: '',
          code: '',
          mobilePwd: '',
          repassword: '',
          check: false
        }
      }).catch(res => {
        this.$nuxt.$loading.finish()
        this.subState = false
        this.$msgBox({
          content: res.msg,
          isShowCancelBtn: false
        })
      })
    }
  }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.login_body {
  background-color: #2256f7;
  height: calc(100vh - 120px);
}

.login_page {
  .login_box {
    margin: 0 auto;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;

    .login_logo img {
      width: 500px;
      height: auto;
    }

    .center_box {
      position: relative;
      margin: 0 100px;
    }
  }

  .login {
    background: rgb(0, 153, 255);

    .login_header {
      width: 1200px;
      margin: 0 auto;
      height: 132px;
      position: relative;

      span {
        position: absolute;
        height: 28px;
        line-height: 28px;
        font-size: 26px;
        color: #fff;
        border-left: 2px solid #fff;
        padding-left: 25px;
        top: 46px;
        left: 210px;
      }
    }
  }

  .login_logo {
    img {
      width: 186px;
      height: 52px;
      margin-top: 32px;
    }
  }

  .login_body {
    img {
      width: 186px;
      height: 52px;
      display: block;
      margin: 0 auto;
    }

    p {
      text-align: center;
      font-size: 14px;
      color: #fff;
      margin-top: 30px;
    }
  }

  .login_form {
    width: 380px;
    border-radius: 6px;
    transition: all 0.8s;
    transform: perspective(600px);

    &.rotate {
      transform: rotateY(-180deg);
    }

    .login_title {
      height: 95px;
      line-height: 95px;
      font-size: 18px;
      padding-left: 30px;
      color: #333;
      background: #fff;
      border-radius: 6px 6px 0 0;

      &.is_login {
        padding-left: 0px;
        text-align: center;
      }
    }
  }

  .form_body {
    background: #fff;
    padding: 0 30px 20px;
    border-radius: 0 0 6px 6px;

    input[type='text'], input[type='password'], input[type='button'], input[type='submit'] {
      width: 310px;
      height: 46px;
      padding-left: 10px;
      margin-top: 20px;
      border-radius: 6px;
      font-size: 14px;
      border-color: rgb(230, 230, 230);

      &.btn {
        background: rgb(213, 20, 35);
        width: 320px;
        color: #fff;
        border: none;
        outline: none;
        cursor: pointer;
        margin-bottom: 20px;
      }
    }

    .error_msg {
      width: 310px;
      color: #D51423;
      font-size: 12px;
    }

    .next_auto {
      font-size: 14px;
      color: #333;
    }

    .is_go {
      float: right;
      color: #0099FF;
      height: 18px;
      line-height: 18px;
      font-size: 14px;
      display: inline-block;
      padding: 0 10px;

      &:hover {
        text-decoration: none;
      }
    }
  }

  .phone_yzm {
    position: relative;

    .phone {
      padding-right: 100px;
      width: 210px;
      font-size: 14px;
      border-color: rgb(230, 230, 230);
      padding-left: 10px;
      height: 46px;
      border-radius: 6px;
      margin-top: 20px;
    }
  }

  .yzm_btn {
    width: 100px;
    height: 46px;
    position: absolute;
    left: 210px;
    top: 21px;
    line-height: 48px;
    border-radius: 0 6px 6px 0;
    text-align: center;
    color: #fff;
    cursor: pointer;
    border: none;
  }

  .login_footer {
    padding-bottom: 30px;

    p {
      text-align: center;
      font-size: 12px;
      color: #1E1E1E;
      margin-top: 20px;
    }
  }

  .img_box {
    height: 70px;

    img {
      width: 70px;
      height: 70px;
      display: block;
      border-radius: 50%;
      margin: 0 auto;
    }
  }

  .hellow_text {
    text-align: center;
    font-size: 14px;
    padding: 20px 0;
    border-bottom: 1px solid #ccc;
  }

  .btn_box {
    border-bottom: 1px solid #ccc;
    padding-top: 20px;
    padding-bottom: 30px;
    font-size: 14px;

    li {
      float: left;
      width: 140px;
      text-align: center;
      line-height: 40px;
      margin: 20px 10px 0;
      border-radius: 6px;

      a {
        display: block;
        background: #f6f8fb;
        border-radius: 6px;

        &:hover {
          color: #fff;
          text-decoration: none;
          background: skyblue;
        }
      }
    }
  }

  .out_btn {
    display: inline-block;
    margin: 10px 0 0 100px;
    font-size: 16px;
    color: #333;
    width: 120px;
    line-height: 30px;
    text-align: center;

    &:hover {
      text-decoration: none;
    }
  }

  .prn_icon {
    width: 16px;
    height: 16px;
    position: relative;
    top: 3px;
  }

  .tabs {
    height: 60px;
    line-height: 60px;
    background: #fff;
    border-radius: 8px 8px 0 0;
    border-bottom: 1px solid rgb(230, 230, 230);

    .tab {
      font-size: 18px;
      width: 50%;
      text-align: center;
      cursor: pointer;
      line-height: 60px;
    }

    span {
      display: inline-block;
      height: 60px;
    }

    .on {
      border-bottom: 2px solid #D51423;
      color: #D51423;
    }
  }

  .r180 {
    transform: rotateY(-180deg);
  }

  .xieyi {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.3);
    z-index: 10003;

    .xieyi_content {
      width: 900px;
      height: 500px;
      margin: 70px auto 0;
      border-radius: 8px;
      background: #fff;
      position: relative;
    }

    .xieyi_title {
      color: #333;
      font-size: 18px;
      line-height: 70px;
      text-align: center;
    }

    .xieyi_body {
      height: 350px;
      overflow-y: auto;
      padding: 0 20px;
    }

    .xieyi_btn {
      position: absolute;
      bottom: 10px;
      width: 166px;
      height: 46px;
      background: rgba(213, 20, 35, 1);
      color: #fff;
      border: none;
      outline: none;
      cursor: pointer;
      left: 50%;
      margin-left: -83px;
      border-radius: 6px;
    }
  }
}
</style>

三、用户密码重设置

<template>
  <div class="register_page">
    <div class="register">
      <div class="register_body">
        <div class="register_header">
          <div class="register_logo">
            <nuxt-link :to="{name: 'index'}">
              <img v-if="websiteInfo && websiteInfo.websiteLogo" :src="websiteInfo.websiteLogo" alt="">
            </nuxt-link>
          </div>
          <p>
            已有账号,
            <nuxt-link :to="{name: 'login'}">立即登陆</nuxt-link>
            <span />
            <nuxt-link :to="{name: 'index'}">返回首页</nuxt-link>
          </p>
        </div>
        <div class="register_content">
          <div class="register_title">修改密码</div>
          <form action="" @submit="resetPsw">
            <div class="form_group">
              <div class="label">手机号:</div>
              <div class="form_ctl">
                <input v-model="obj.mobile" type="text" maxlength="11" @change="enterPhone">
                <p v-show="errTip0" class="err">{{ errTip0 }}</p>
              </div>
            </div>
            <div class="form_group">
              <div class="label">验证码:</div>
              <div class="form_ctl">
                <input v-model="obj.code" type="text" maxlength="6" name="code" @change="enter">
                <y-button :mobile="obj.mobile" />
                <p v-show="errTip1" class="err">{{ errTip1 }}</p>
              </div>
            </div>
            <div class="form_group">
              <div class="label">新密码:</div>
              <div class="form_ctl">
                <input v-model="obj.mobilePwd" type="password" name="mobilePwd" @change="enter">
                <p v-show="errTip2" class="err">{{ errTip2 }}</p>
              </div>
            </div>
            <div class="form_group">
              <div class="label">重复密码:</div>
              <div class="form_ctl">
                <input v-model="obj.repassword" type="password" name="repassword" @change="enter">
                <p v-show="errTip3" class="err">{{ errTip3 }}</p>
              </div>
            </div>
            <div class="form_group">
              <div class="label">&nbsp;</div>
              <div class="form_ctl">
                <input type="submit" class="submit_btn" value="确定">
              </div>
            </div>
          </form>
        </div>
      </div>
      <!-- <y-bottom /> -->
    </div>
  </div>
</template>
<script>
import YButton from '@/components/common/Code'
// import YBottom from '@/components/common/Bottom'
import { updatePassword } from '~/api/login.js'

export default {
  components: {
    YButton
    // YBottom
  },
  data() {
    return {
      errTip0: '',
      errTip1: '',
      errTip2: '',
      errTip3: '',
      obj: {
        mobile: '',
        code: '',
        mobilePwd: '',
        repassword: ''
      },
      websiteInfo: this.$store.state.websiteInfo
    }
  },
  head() {
    return {
      title: '修改密码-' + this.websiteInfo.websiteName
    }
  },
  mounted() {

  },
  methods: {
    goLogin() {
      this.$router.push({ path: '/login' })
    },
    // 输入内容
    enter(e) {
      const name = e.target.name
      if (name === 'code') {
        if (this.obj.code.length !== 6) {
          this.errTip1 = '请输入正确的手机验证码'
          return false
        } else {
          this.errTip1 = false
        }
      } else if (name === 'mobilePwd') {
        if (this.obj.mobilePwd.length < 6 || this.obj.mobilePwd.length > 16) {
          this.errTip2 = '请输入6-16位的登录密码,区分大小写,不可有空格'
          return false
        } else {
          this.errTip2 = false
        }
      } else if (name === 'repassword') {
        if (this.obj.mobilePwd !== this.obj.repassword) {
          this.errTip3 = '两次输入密码不一致'
          return false
        } else {
          this.errTip3 = false
        }
      }
    },
    // 输入手机
    enterPhone() {
      if (this.obj.mobile.length === 11) {
        if ((/^1[3|4|5|8|7][0-9]\d{4,8}$/.test(this.obj.mobile.trim()))) {
          this.errTip0 = false
          this.getCodeBtn = true
        } else {
          this.errTip0 = '请输入正确手机'
          this.getCodeBtn = false
        }
      } else {
        this.errTip0 = false
        this.getCodeBtn = false
      }
    },
    showMsg(msg) {
      this.$msgBox({
        content: msg,
        isShowCancelBtn: false
      }).catch(() => {
      })
    },
    resetPsw(e) {
      e.preventDefault()
      if (!(/^1[3|4|5|8|7][0-9]\d{4,8}$/.test(this.obj.mobile.trim()))) {
        this.showMsg('请输入正确格式的手机号码')
        return
      }
      if (this.obj.code.length !== 6) {
        this.showMsg('请输入正确的验证码')
        return
      }
      if (this.obj.mobilePwd.length < 6 || this.obj.mobilePwd.length > 16) {
        this.showMsg('请输入6-16位的登录密码,区分大小写,不可有空格')
        return
      }
      if (this.obj.mobilePwd !== this.obj.repassword) {
        this.showMsg('两次输入密码不一致')
        return
      }
      updatePassword(this.obj).then(res => {
        this.$msgBox({
          content: '修改成功,请重新登录',
          isShowCancelBtn: false
        }).then(() => {
          this.$store.commit('SIGN_OUT')
          this.$router.push({ name: 'login' })
        }).catch(() => {
          this.$store.commit('SIGN_OUT')
          this.$router.push({ name: 'login' })
        })
      })
    }
  }
}
</script>
<style lang="scss" rel="stylesheet/scss">
.register_page {
  .register {
    background: rgb(247, 247, 247);
    .register_body {
      width: 900px;
      margin: 0 auto 30px;
    }

    .footer_text {
      text-align: center;
      font-size: 12px;
      color: #999;
      padding: 10px 0;
    }

    .register_header {
      height: 112px;
      padding-top: 30px;
      position: relative;

      .register_logo {
        position: absolute;
        top: 60px;

        img {
          height: 52px;
        }
      }

      p {
        font-size: 14px;
        text-align: right;
        height: 18px;
        line-height: 18px;
        margin-top: 48px;

        a {
          color: #0099FF;

          &:hover {
            text-decoration: none;
          }
        }

        span {
          display: inline-block;
          width: 1px;
          height: 18px;
          background: #333;
          position: relative;
          top: 4px;
          margin: 0 10px;
        }
      }
    }

    .register_content {
      border-radius: 8px;
      background: #fff;
      min-height: 500px;
      margin-top: 20px;

      .register_title {
        border-radius: 8px 8px 0 0;
        height: 80px;
        line-height: 80px;
        background: rgb(228, 228, 228);
        font-size: 18px;
        text-align: center;
        color: #333;
      }
    }

    .m_center {
      width: 390px;
      margin: 30px auto;
    }
  }

  .prn_icon {
    width: 16px;
    height: 16px;
    position: relative;
    top: 3px;
  }

  .form_group {
    width: 432px;
    margin: 20px auto 0;
    font-size: 14px;

    .label {
      text-align: right;
      float: left;
      display: block;
      width: 110px;
      line-height: 36px;
      color: #333;
    }

    .form_ctl {
      margin-left: 12px;
      float: left;
      position: relative;

      input {
        width: 250px;
        height: 46px;
        padding-left: 10px;
        border-radius: 6px;
        font-size: 14px;
        margin-top: -5px;

        &.reset_yzm {
          width: 310px;
          padding-right: 110px;
        }
      }

      .submit_btn {
        width: 250px;
        background: rgb(213, 20, 35);
        border: none;
        color: #fff;
        cursor: pointer;
      }

      .yzm_btn {
        width: 100px;
        height: 46px;
        position: absolute;
        left: 150px;
        top: -5px;
        line-height: 48px;
        background: rgb(213, 20, 35);
        border-radius: 0 6px 6px 0;
        text-align: center;
        color: #fff;
        cursor: pointer;
        border: none;

        &:disabled {
          background: rgb(204, 204, 204);
        }
      }

      .err {
        color: rgb(213, 20, 35);
      }
    }

    .text {
      color: #333;
      line-height: 36px;
    }

    &:after {
      content: '';
      display: block;
      clear: both;
    }
  }

  .login_footer {
    padding-bottom: 30px;

    p {
      text-align: center;
      font-size: 12px;
      color: #1E1E1E;
      margin-top: 20px;
    }
  }
}
</style>

四、课程搜索界面

<script>
import YHeader from '~/components/common/Header'
import RightTap from '@/components/common/Top'
import { courseList } from '~/api/course'
import DPage from '~/components/common/Page'

export default {
  name: 'Search',
  components: {
    YHeader,
    DPage,
    RightTap
  },
  async asyncData(context) {
    const dataObj = {
      map: {
        courseName: '',
        isHfield: 1
      },
      opts: {
        list: []
      },
      page: {
        pageCurrent: 1,
        pageSize: 10,
        totalPage: 1,
        totalCount: 0
      }
    }
    dataObj.websiteInfo = context.store.state.websiteInfo
    try {
      dataObj.map.courseName = context.query.search
      const res = await courseList(dataObj.map, dataObj.page.pageCurrent, dataObj.page.pageSize)
      dataObj.opts.list = res.list || []
      dataObj.page.pageCurrent = res.pageCurrent || 1
      dataObj.page.totalPage = res.totalPage || 1
      dataObj.page.totalCount = res.totalCount || 0
      return dataObj
    } catch (e) {
      context.error({ message: 'Data not found', statusCode: 404 })
    }
  },
  data() {
    return {
      openVip: false,

      ctrl: {
        loading: false
      }

    }
  },
  head() {
    return {
      title: '搜索页面-' + this.$store.state.websiteInfo.websiteName,
      meta: [
        {
          hid: 'keywords',
          name: 'keywords',
          content: this.$store.state.websiteInfo.websiteKeyword
        },
        {
          hid: 'description',
          name: 'description',
          content: this.$store.state.websiteInfo.websiteDesc
        }
      ]
    }
  },
  watch: {
    '$route'() {
      this.getList()
    }
  },
  mounted() {
    if (this.websiteInfo && this.websiteInfo.isEnableVip) {
      this.openVip = true
    }
  },
  methods: {
    getPage(int) {
      window.scrollTo(0, 0)
      this.page.pageCurrent = int
      this.getList()
    },
    handleSearch() {
      this.getList()
    },
    getList() {
      this.ctrl.loading = true
      courseList(this.map, this.page.pageCurrent, this.page.pageSize).then(res => {
        this.ctrl.loading = false
        this.opts.list = res.list || []
        this.page.pageCurrent = res.pageCurrent
        this.page.totalPage = res.totalPage || 1
        this.page.totalCount = res.totalCount
      }).catch(() => {
        this.ctrl.loading = false
        this.opts.list = []
        this.page.pageCurrent = 1
        this.page.totalPage = 1
        this.page.totalCount = 0
      })
    }
  }
}
</script>

<style scoped lang="scss">
// .search_page {
//   min-height: 100vh;
// }

@keyframes bganimation {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

.search_input {
  padding: 33px 0;
  background-image: linear-gradient(125deg, #F44336, #E91E63, #9C27B0, #3F51B5, #2196F3);
  animation: bganimation 15s infinite;

  .search_box_content {
    width: 607px;
    margin: 0 auto;
  }

  .form {
    width: 100%;

    position: relative;

    .iconfont {
      position: absolute;
      left: 10px;
      top: 2px;
      line-height: 44px;
      font-size: 22px;
      margin-right: 2px;
      color: #999;
      font-weight: 700;
    }

    .search_box_input {
      width: 505px;
      padding-left: 40px;
      height: 44px;
      line-height: 44px;
      background: #fff;
      border-radius: 6px 0 0 6px;
      font-size: 13px;
      color: #333;
    }

    .search_btn {
      width: 96px;
      height: 44px;
      background-color: #47a6ee;
      color: #fff;
      font-weight: 700;
      border-radius: 0 6px 6px 0;
      position: relative;
      top: 2px;
      left: -5px;
      border: none;
      font-size: 14px;
      cursor: pointer;
    }
  }
}

.course_info {
  display: block;
  width: 285px;
  height: 240px;
  border-radius: 6px;
  position: relative;

  &:hover {
    text-decoration: none;
    color: #000;
    box-shadow: 0px 3px 18px rgba(0, 0, 0, 0.2);
    transform: translateY(-2px);
    transition: all .3s;
  }

  .img_box {
    position: relative;
    width: 285px;
    height: 140px;

    .qizi {
      background: url(../assets/image/activity/qizi.png) no-repeat center;
      position: absolute;
      top: 0;
      left: 10px;
      width: 46px;
      height: 43px;
      padding-top: 3px;
      color: #fff;
      font-size: 14px;
      text-align: center;
    }

    .course_img {
      width: 285px;
      height: 140px;
      border-radius: 6px 6px 0 0;
    }
  }

  p {
    font-size: 16px;
    margin-top: 5px;
    padding: 0 10px;
    word-break: break-all;
  }

  .price_box {
    font-size: 16px;
    position: absolute;
    bottom: 15px;
    left: 10px;
    color: red;
  }
}

.search_content {
  background: #f6f8fb;
  padding: 30px 0 10px 0;
  min-height: calc(100vh - 130px - 120px - 380px - 1px);

  ul {
    width: 1200px;
    margin: 0 auto;
  }

  li {
    float: left;
    border-radius: 6px;
    background: #fff;
    margin: 0px 20px 20px 0px;

    &:nth-child(4n) {
      margin-right: 0px;
    }
  }
}

.no_data {
  text-align: center;
  width: 1200px;
  font-size: 20px;
  line-height: 100px;
  color: #999;
  background: #fff;
  margin: 20px auto;
  border-radius: 6px;
  box-shadow: 0 4px 10px 0 rgb(0 0 0 / 5%);
}
</style>

更多详细代码看idea

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值