Selaa lähdekoodia

添加视屏详情页面

lex-xin 3 vuotta sitten
vanhempi
commit
74b0014385

+ 33 - 2
package-lock.json

@@ -3330,8 +3330,7 @@
     "core-js": {
       "version": "3.21.1",
       "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.21.1.tgz",
-      "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==",
-      "dev": true
+      "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig=="
     },
     "core-js-compat": {
       "version": "3.19.1",
@@ -3403,6 +3402,11 @@
       "resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz",
       "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
     },
+    "custom-event-polyfill": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz",
+      "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w=="
+    },
     "dayjs": {
       "version": "1.10.7",
       "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.10.7.tgz",
@@ -5110,6 +5114,11 @@
       "resolved": "https://registry.npmmirror.com/loaders.css/-/loaders.css-0.1.2.tgz",
       "integrity": "sha512-Rhowlq24ey1VOeor+3wYOt9+MjaxBOJm1u4KlQgNC3+0xJ0LS4wq4iG57D/BPzvuD/7HHDGQOWJ+81oR2EI9bQ=="
     },
+    "loadjs": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/loadjs/-/loadjs-4.2.0.tgz",
+      "integrity": "sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA=="
+    },
     "lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
@@ -5853,6 +5862,18 @@
         }
       }
     },
+    "plyr": {
+      "version": "3.6.12",
+      "resolved": "https://registry.npmmirror.com/plyr/-/plyr-3.6.12.tgz",
+      "integrity": "sha512-42WhYpMS/FEyX2unSEvhYtj1RvJgWvOsjZQFDongOQHA4eVzsyr7b06bzVpinMAOVC9e5H7RCbK+6CCAFIl2VQ==",
+      "requires": {
+        "core-js": "^3.20.0",
+        "custom-event-polyfill": "^1.0.7",
+        "loadjs": "^4.2.0",
+        "rangetouch": "^2.0.1",
+        "url-polyfill": "^1.1.12"
+      }
+    },
     "postcss": {
       "version": "8.4.5",
       "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.5.tgz",
@@ -6079,6 +6100,11 @@
       "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
       "dev": true
     },
+    "rangetouch": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/rangetouch/-/rangetouch-2.0.1.tgz",
+      "integrity": "sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA=="
+    },
     "readable-stream": {
       "version": "3.6.0",
       "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz",
@@ -6681,6 +6707,11 @@
         "punycode": "^2.1.0"
       }
     },
+    "url-polyfill": {
+      "version": "1.1.12",
+      "resolved": "https://registry.npmmirror.com/url-polyfill/-/url-polyfill-1.1.12.tgz",
+      "integrity": "sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A=="
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "mitt": "^3.0.0",
     "normalize.css": "^8.0.1",
     "numeral": "^2.0.6",
+    "plyr": "^3.6.12",
     "query-string": "^7.1.1",
     "umi-request": "^1.4.0",
     "vant": "^3.4.6",

+ 1 - 0
src/business-components/course-video-item/index.tsx

@@ -15,6 +15,7 @@ interface IDetail {
   title: string;
   content?: string;
   imgUrl?: string;
+  videoUrl?: string;
 }
 
 export default defineComponent({

+ 15 - 0
src/components/col-video/index.module.less

@@ -0,0 +1,15 @@
+.video-container {
+  width: 100%;
+  --plyr-color-main: #01c1b5;
+
+  video {
+    width: 100%;
+    object-fit: cover;
+  }
+
+  :global {
+    .plyr__poster {
+      background-size: cover;
+    }
+  }
+}

+ 72 - 0
src/components/col-video/index.tsx

@@ -0,0 +1,72 @@
+import { defineComponent } from "vue";
+import styles from "./index.module.less";
+import Plyr from "plyr";
+import 'plyr/dist/plyr.css';
+import { browser } from "@/helpers/utils";
+export default defineComponent({
+  name: 'col-video',
+  props: {
+    setting: {
+      type: Object,
+      default: () => { }
+    },
+    controls: Boolean,
+    height: String,
+    src: {
+      type: String,
+      default: ''
+    },
+    poster: {
+      type: String,
+      default: ''
+    },
+    styleValue: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      player: null as any
+    }
+  },
+  mounted() {
+    this._init()
+  },
+  methods: {
+    _init() {
+      // controls: [
+      //   'play-large' ,  // 中间的大播放按钮
+      //   'restart' ,  // 重新开始播放
+      //   'rewind' ,  // 按寻道时间倒带(默认 10 秒)
+      //   'play' ,  // 播放/暂停播放
+      //   'fast-forward' ,  // 快进查找时间(默认 10 秒)
+      //   'progress' ,  // 播放和缓冲的进度条和滑动条
+      //   'current-time' ,  // 播放的当前时间
+      //   ' duration' ,  // 媒体的完整持续时间
+      //   'mute' ,  // 切换静音
+      //   'volume', // 音量控制
+      //   'captions' ,  // 切换字幕
+      //   'settings' ,  // 设置菜单
+      //   'pip' ,  // 画中画(当前仅 Safari)
+      //   'airplay' ,  // Airplay(当前仅 Safari)
+      //   'download ' ,  // 显示一个下载按钮,其中包含指向当前源或您在选项中指定的自定义 URL 的链接
+      //   'fullscreen' ,  // 切换全屏
+      // ] ;
+      // 'current-time',
+      let controls = ['play-large', 'play', 'progress', 'captions', 'fullscreen']
+      this.player = new Plyr((this as any).$refs.video, {
+        controls: controls,
+        ...this.setting,
+      })
+      this.player.elements.container ? this.player.elements.container.style.height = this.height || '210px' : null
+    }
+  },
+  render() {
+    return (
+      <div class={styles['video-container']}>
+        <video ref="video" src={this.src} poster={this.poster} style={{ ...this.styleValue }} />
+      </div>
+    )
+  }
+})

+ 8 - 0
src/router/routes-teacher.ts

@@ -85,6 +85,14 @@ export default [
         meta: {
           title: '视频课详情'
         }
+      },
+      {
+        path: '/videoClassDetail',
+        name: 'videoClassDetail',
+        component: () => import('@/teacher/video-class/video-class-detail'),
+        meta: {
+          title: '视频课详情'
+        }
       }
     ]
   },

+ 79 - 0
src/teacher/video-class/video-class-detail.module.less

@@ -0,0 +1,79 @@
+.video-class-detail {
+  .cell {
+    margin-bottom: 6px;
+  }
+  .title {
+    padding-left: 8px;
+    font-size: 15px;
+    font-weight: 500;
+    color: #1a1a1a;
+    flex: 1 auto;
+  }
+  .label {
+    font-size: 14px;
+    span {
+      color: var(--van-primary);
+    }
+  }
+
+  :global {
+    .van-tab--active {
+      color: var(--van-primary);
+    }
+    .van-field__value {
+      background: #f7f7f7;
+      border-radius: 30px;
+      padding-left: 15px;
+    }
+  }
+
+  .open-teacher-info {
+    margin: 12px 14px 8px;
+    width: auto;
+    background-color: #fff;
+    border-radius: 8px;
+    padding: 14px 15px;
+    .userLogo {
+      width: 48px;
+      height: 48px;
+      border-radius: 50%;
+      overflow: hidden;
+    }
+    :global {
+      .van-cell__value {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+      }
+    }
+
+    .teacher-info {
+      display: flex;
+      justify-content: center;
+      flex-direction: column;
+      padding-left: 8px;
+      .users {
+        display: flex;
+        padding-top: 5px;
+      }
+      .teacher-name {
+        font-weight: 500;
+        color: #1a1a1a;
+        line-height: 1.2;
+        font-size: 16px;
+        padding-right: 8px;
+      }
+      .level {
+        padding-top: 5px;
+        line-height: 1.2;
+        font-size: 12px;
+        color: #999999;
+      }
+    }
+
+    .teacher-desc {
+      display: flex;
+      align-items: center;
+    }
+  }
+}

+ 143 - 3
src/teacher/video-class/video-class-detail.tsx

@@ -1,16 +1,156 @@
+import CourseVideoItem from "@/business-components/course-video-item";
+import SectionDetail from "@/business-components/section-detail";
+import ColHeader from "@/components/col-header";
+import ColVideo from "@/components/col-video";
+import request from "@/helpers/request";
+import { Cell, Icon, Rate, Tab, Tabs, Image, Button, Sticky, Field } from "vant";
 import { defineComponent } from "vue";
+import styles from './video-class-detail.module.less';
+import { state } from "@/state";
+
+import iconTeacher from '@common/images/icon_teacher.png';
 
 export default defineComponent({
   name: 'VideoClassDetail',
   data() {
+    const query = this.$route.query
     return {
-
+      groupId: query.groupId,
+      classId: query.classId,
+      tabIndex: 1,
+      title: '',
+      detailList: [],
+      posterUrl: '',
+      srcUrl: '',
+      message: ''
+    }
+  },
+  computed: {
+    users() {
+      return state.user.data
+    }
+  },
+  async mounted() {
+    try {
+      const res = await request.get('/api-teacher/videoLessonGroup/selectVideoLesson', {
+        params: {
+          groupId: this.groupId
+        }
+      })
+      const result = res.data || {}
+      this.title = result.lessonGroup.lessonName
+      this.detailList = result.detailList || []
+      this.detailList.forEach((item: any) => {
+        if (item.id === Number(this.classId)) {
+          this.posterUrl = item.coverUrl
+          this.srcUrl = item.videoUrl
+        }
+      })
+    } catch { }
+  },
+  methods: {
+    onPlay(item: any) {
+      this.posterUrl = item.imgUrl
+      this.srcUrl = item.videoUrl
     }
   },
   render() {
     return (
-      <div>
-        我的课程视频课程详情
+      <div class={styles['video-class-detail']}>
+        <ColHeader v-slots={{
+          content: () => (
+            <ColVideo src={this.srcUrl} poster={this.posterUrl} />
+          )
+        }} />
+        <Cell border={false} class={styles.cell} title={this.title} titleClass={styles.title} v-slots={{
+          icon: () => (<Icon name="video" size={18} style={{ display: 'flex', alignItems: 'center' }} />),
+          value: () => (<div class={styles.label}><span>1</span>/4课时</div>)
+        }}></Cell>
+
+        <Tabs v-model:active={this.tabIndex} class={styles.infoField} color="var(--van-primary)" lineWidth={20}>
+          <Tab title="目录" name={1}>
+            <div style={{ height: 'calc(100vh - 320px)', overflowY: 'auto' }}>
+              <SectionDetail title="课程列表" icon="courseList" >
+                {this.detailList.map((item: any) => (
+                  <CourseVideoItem class={'mb12'} detail={{
+                    id: item.id,
+                    title: item.videoTitle,
+                    content: item.videoContent,
+                    imgUrl: item.coverUrl,
+                    videoUrl: item.videoUrl
+                  }} onPlay={this.onPlay} />
+                ))}
+                {this.detailList.map((item: any) => (
+                  <CourseVideoItem class={'mb12'} detail={{
+                    id: item.id,
+                    title: item.videoTitle,
+                    content: item.videoContent,
+                    imgUrl: item.coverUrl,
+                    videoUrl: item.videoUrl
+                  }} onPlay={this.onPlay} />
+                ))}
+                {this.detailList.map((item: any) => (
+                  <CourseVideoItem class={'mb12'} detail={{
+                    id: item.id,
+                    title: item.videoTitle,
+                    content: item.videoContent,
+                    imgUrl: item.coverUrl,
+                    videoUrl: item.videoUrl
+                  }} onPlay={this.onPlay} />
+                ))}
+                {this.detailList.map((item: any) => (
+                  <CourseVideoItem class={'mb12'} detail={{
+                    id: item.id,
+                    title: item.videoTitle,
+                    content: item.videoContent,
+                    imgUrl: item.coverUrl,
+                    videoUrl: item.videoUrl
+                  }} onPlay={this.onPlay} />
+                ))}
+              </SectionDetail>
+            </div>
+
+          </Tab>
+          <Tab title="简介" name={2}>
+            <Cell class={styles['open-teacher-info']} border={false} v-slots={{
+              icon: () => (<Image class={styles.userLogo} src={this.users?.headUrl || iconTeacher} fit="cover" />)
+            }}>
+              <div class={styles['teacher-info']}>
+                <div class={styles['users']}>
+                  <div class={styles['teacher-name']}>
+                    {this.users?.username}
+                  </div>
+                  <Rate modelValue={this.users?.starGrade} color="#FFC459" void-icon="star" voidColor="#D6D6D6" size={15} />
+                </div>
+                <div class={styles.level}>
+                  粉丝{this.users?.fansNum}
+                </div>
+              </div>
+              <div class={styles['teacher-desc']}>
+                <Button type="primary" icon="plus" size="mini" style={{ borderRadius: '5px', padding: '0 8px' }}>关注</Button>
+              </div>
+            </Cell>
+            <SectionDetail>
+              <p class={styles.introduction}>课程介绍</p>
+            </SectionDetail>
+          </Tab>
+          <Tab title="讨论" name={3}>
+            <div style={{ height: 'calc(100vh - 365px)', overflowY: 'auto' }}>
+              <div style={{ height: '20vh' }}>主体请求</div>
+            </div>
+
+            <Sticky offsetBottom={0} position='bottom'>
+              <Field
+                placeholder="快来讨论吧~"
+                v-model={this.message}
+                v-slots={{
+                  button: () => (
+                    <Button type="primary" disabled={!this.message} style={{ padding: '0 20px' }} size="small" round>发布</Button>
+                  )
+                }} />
+            </Sticky>
+          </Tab>
+        </Tabs>
       </div>
     );
   }

+ 8 - 3
src/teacher/video-class/video-detail.tsx

@@ -12,7 +12,6 @@ import ColResult from "@/components/col-result";
 export default defineComponent({
   name: 'VideoDetail',
   data() {
-    console.log(this.$route.query.groupId)
     const query = this.$route.query
     return {
       userInfo: {} as any,
@@ -67,14 +66,20 @@ export default defineComponent({
         }
         this.buyUserList = this.buyUserList.concat(result.list || [])
         this.finished = result.pageNo >= result.totalPage
-        this.params = result.pageNo + 1
+        this.params.page = result.pageNo + 1
         this.dataShow = this.buyUserList.length > 0
 
         this.userInfo.buyNum = result.totalCount || 0
       } catch { }
     },
     onPlay(detail: any) {
-      console.log(detail);
+      this.$router.push({
+        path: '/videoClassDetail',
+        query: {
+          groupId: this.params.groupId,
+          classId: detail.id
+        }
+      })
     }
   },
   render() {