Browse Source

课件播放

liushengqiang 1 year ago
parent
commit
0f7e163763
41 changed files with 2407 additions and 8 deletions
  1. 12 0
      src/helpers/utils.ts
  2. 2 2
      src/styles/index.less
  3. 83 0
      src/views/courseware-play/component/musicScore.module.less
  4. 130 0
      src/views/courseware-play/component/musicScore.tsx
  5. 110 0
      src/views/courseware-play/component/point.module.less
  6. 102 0
      src/views/courseware-play/component/points.tsx
  7. 32 0
      src/views/courseware-play/component/tool.module.less
  8. 40 0
      src/views/courseware-play/component/tool.tsx
  9. 43 0
      src/views/courseware-play/component/tools/pen.module.less
  10. 141 0
      src/views/courseware-play/component/tools/pen.tsx
  11. 164 0
      src/views/courseware-play/component/video-play.tsx
  12. 133 0
      src/views/courseware-play/component/video.module.less
  13. 0 0
      src/views/courseware-play/datas/data.json
  14. 11 0
      src/views/courseware-play/image/back.svg
  15. 15 0
      src/views/courseware-play/image/icon-arrow.svg
  16. 24 0
      src/views/courseware-play/image/icon-dian.svg
  17. 22 0
      src/views/courseware-play/image/icon-down.svg
  18. 19 0
      src/views/courseware-play/image/icon-image-active.svg
  19. 19 0
      src/views/courseware-play/image/icon-image.svg
  20. BIN
      src/views/courseware-play/image/icon-load.gif
  21. 23 0
      src/views/courseware-play/image/icon-loop-active.svg
  22. 20 0
      src/views/courseware-play/image/icon-loop.svg
  23. 22 0
      src/views/courseware-play/image/icon-menu.svg
  24. BIN
      src/views/courseware-play/image/icon-more.png
  25. 28 0
      src/views/courseware-play/image/icon-mulv.svg
  26. 32 0
      src/views/courseware-play/image/icon-pause.svg
  27. BIN
      src/views/courseware-play/image/icon-pen.png
  28. 22 0
      src/views/courseware-play/image/icon-play.svg
  29. 24 0
      src/views/courseware-play/image/icon-point.svg
  30. 20 0
      src/views/courseware-play/image/icon-song-active.svg
  31. 20 0
      src/views/courseware-play/image/icon-song.svg
  32. 53 0
      src/views/courseware-play/image/icon-start.svg
  33. 4 0
      src/views/courseware-play/image/icon-touping.svg
  34. 22 0
      src/views/courseware-play/image/icon-up.svg
  35. 9 0
      src/views/courseware-play/image/icon-video-active.svg
  36. 9 0
      src/views/courseware-play/image/icon-video.svg
  37. BIN
      src/views/courseware-play/image/icon-videobg.png
  38. 1 0
      src/views/courseware-play/image/icon-zhibo.svg
  39. 250 0
      src/views/courseware-play/index.module.less
  40. 634 6
      src/views/courseware-play/index.tsx
  41. 112 0
      src/views/courseware-play/playRecordTime.tsx

+ 12 - 0
src/helpers/utils.ts

@@ -132,3 +132,15 @@ export const toChinesNum = (num: any) => {
   }
   return overWan ? getWan(overWan) + '万' + getWan(noWan) : getWan(num);
 };
+
+// 秒转分
+export const getSecondRPM = (second: number, type?: string) => {
+  if (isNaN(second)) return '00:00'
+  const mm = Math.floor(second / 60).toString().padStart(2, '0')
+  const dd = Math.floor(second % 60).toString().padStart(2, '0')
+  if (type === 'cn') {
+    return mm + '分' + dd + '秒'
+  } else {
+    return mm + ':' + dd
+  }
+}

+ 2 - 2
src/styles/index.less

@@ -1,6 +1,6 @@
 :root:root {
-  --k-primary: #01c1b5; // 主题色
-  --k-font-primary: #00b2a7; // 字体色
+  --k-primary: #1CACF1; // 主题色
+  --k-font-primary: #1CACF1; // 字体色
   --van-pull-refresh-head-height: 55px;
 
   --van-skeleton-paragraph-background: #ECEEF3;

+ 83 - 0
src/views/courseware-play/component/musicScore.module.less

@@ -0,0 +1,83 @@
+.musicScore {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  -webkit-overflow-scrolling: touch;
+  overflow: scroll;
+  .container {
+    position: relative;
+    display: block;
+    border: none;
+    width: 100%;
+    height: 100%;
+    z-index: 10;
+  }
+  .musicModel {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+  }
+}
+.errorModel {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: #000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+
+.startBtn{
+  position: absolute;
+  left: 50%;
+  bottom: 6vh;
+  transform: translateX(-50%);
+  z-index: 11;
+  &:active{
+    opacity: .8;
+  }
+}
+.loading{
+  position: absolute;
+  left: 4%;
+  top: 50%;
+  margin-top: -15Px;
+}
+.skeletonWrap{
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  height: 100%;
+  z-index: 1;
+  padding-top: 1.2rem;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  overflow: hidden;
+  background: #fff;
+  pointer-events: none;
+}
+.skeleton {
+  --van-skeleton-paragraph-height: 24px;
+  :global{
+    .van-skeleton__content{
+      .van-skeleton-paragraph{
+        margin: 12px auto;
+        width: 80% !important;
+        &:first-child{
+          width: 60% !important;
+        }
+        &:last-child{
+          width: 100% !important;
+        }
+      }
+    }
+  }
+}

+ 130 - 0
src/views/courseware-play/component/musicScore.tsx

@@ -0,0 +1,130 @@
+import { defineComponent, ref, nextTick, onMounted, watch } from 'vue'
+import styles from './musicScore.module.less'
+import qs from 'query-string'
+import iconStart from '../image/icon-start.svg'
+import { listenerMessage, postMessage } from '@/helpers/native-message'
+import { Loading, Skeleton } from 'vant'
+import { usePageVisibility } from '@vant/use'
+import { useRoute } from 'vue-router'
+import { browser } from '@/helpers/utils'
+
+export default defineComponent({
+  name: 'musicScore',
+  props: {
+    music: {
+      type: Object,
+      default: () => {}
+    },
+    activeModel: {
+      type: Boolean
+    }
+  },
+  emits: ['setIframe'],
+  setup(props, { emit }) {
+    const browserInfo = browser()
+    const route = useRoute()
+    const isLoading = ref(false)
+    const pageVisibility = usePageVisibility()
+    /** 页面显示和隐藏 */
+    watch(pageVisibility, (value) => {
+      console.log("🚀 ~ value:", value)
+      if (value == 'hidden') {
+        isLoading.value = false
+      }
+    })
+    const iframeRef = ref()
+    const isLoaded = ref(false)
+    const renderError = ref(false)
+    const renderSuccess = ref(false)
+    const Authorization = sessionStorage.getItem('Authorization') || ''
+    const origin = /(localhost|192)/.test(location.host)
+      ? 'https://test.lexiaoya.cn'
+      : location.origin
+    const query = qs.stringify({
+      id: props.music.content,
+      modelType: 'practice',
+      headerHeight: 32,
+      Authorization: Authorization
+    })
+    let src = `https://test.lexiaoya.cn/orchestra-music-score/?_t=1687590480955&id=11707&modelType=practice&modeType=json&Authorization=bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyLXJlc291cmNlIl0sImNsaWVudFR5cGUiOiJCQUNLRU5EIiwidXNlcl9uYW1lIjoiMTgxNjI4NTU4MDAiLCJzY29wZSI6WyJhbGwiXSwidXNlcklkIjoiMTAwMDE0OSIsImF1dGhvcml0aWVzIjpbIjE4MTYyODU1ODAwIl0sImp0aSI6IjY0MzA2NjllLTE5NGItNDk3Yy1hMDQ5LWM4YWUxMGU0NDNiOCIsImNsaWVudF9pZCI6ImptZWR1LWJhY2tlbmQifQ.aeJ0o-lSfx1Ok-YptZuC-AUY6M7k3LK9rr0Bmx7sj81pPt2HDiDqeT2PuriYdJacxWnxboyhdG_lwU0QK-W-vON97c45NQpSEFLVpZ0m1xdIqwllwf20xhyj5YJwdOFUzxf1TNEfGsHZg66J7wEJQBSzlmQwcxmEE5lqLVD8o_3f2SBqnWCj9RqE4air7FUemllMnZiu8HsS-TKtLDaGa_XW8Yb_Zjzzz6r5UcYNI-C1uKPXg18o1dhHBJ8O-Pl0U8WivPRub_opvO2NSn5L9YtPt7Dd50UeSwaIOdMeCGdii1bg__h77Stek1_5IaZLYkoo2gvmUA-xk09TwCQBpA`
+    const checkView = () => {
+      fetch(src)
+        .then(() => {
+          renderSuccess.value = true
+          renderError.value = false
+        })
+        .catch((err) => {
+          renderError.value = true
+        })
+    }
+    watch(props.music, () => {
+      if (renderSuccess.value) return
+      renderError.value = false
+      if (props.music.display) {
+        checkView()
+      }
+    })
+
+    // 去云教练完整版
+    const gotoAccomany = () => {
+      if (isLoading.value) return
+      if (!browserInfo.ios){
+        isLoading.value = true
+      }
+      const parmas = qs.stringify({
+        id: props.music.content
+      })
+      // let src = `${location.origin}/orchestra-music-score/?` + parmas
+      let src = `https://test.lexiaoya.cn/orchestra-music-score/?_t=1687590480955&id=11707&modelType=practice&modeType=json&Authorization=bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyLXJlc291cmNlIl0sImNsaWVudFR5cGUiOiJCQUNLRU5EIiwidXNlcl9uYW1lIjoiMTgxNjI4NTU4MDAiLCJzY29wZSI6WyJhbGwiXSwidXNlcklkIjoiMTAwMDE0OSIsImF1dGhvcml0aWVzIjpbIjE4MTYyODU1ODAwIl0sImp0aSI6IjY0MzA2NjllLTE5NGItNDk3Yy1hMDQ5LWM4YWUxMGU0NDNiOCIsImNsaWVudF9pZCI6ImptZWR1LWJhY2tlbmQifQ.aeJ0o-lSfx1Ok-YptZuC-AUY6M7k3LK9rr0Bmx7sj81pPt2HDiDqeT2PuriYdJacxWnxboyhdG_lwU0QK-W-vON97c45NQpSEFLVpZ0m1xdIqwllwf20xhyj5YJwdOFUzxf1TNEfGsHZg66J7wEJQBSzlmQwcxmEE5lqLVD8o_3f2SBqnWCj9RqE4air7FUemllMnZiu8HsS-TKtLDaGa_XW8Yb_Zjzzz6r5UcYNI-C1uKPXg18o1dhHBJ8O-Pl0U8WivPRub_opvO2NSn5L9YtPt7Dd50UeSwaIOdMeCGdii1bg__h77Stek1_5IaZLYkoo2gvmUA-xk09TwCQBpA`
+      postMessage({
+        api: 'openAccompanyWebView',
+        content: {
+          url: src,
+          orientation: 0,
+          isHideTitle: true,
+          statusBarTextColor: false,
+          isOpenLight: true
+        }
+      }, () => {
+        if (browserInfo.ios){
+          isLoading.value = true
+        }
+      })
+    }
+    listenerMessage('webViewOnResume', () => {
+      isLoading.value = false
+    })
+
+    return () => (
+      <div class={styles.musicScore}>
+        <iframe
+          ref={iframeRef}
+          onLoad={(e: Event) => {
+            emit('setIframe', iframeRef.value)
+            isLoaded.value = true
+          }}
+          class={[styles.container, 'musicIframe']}
+          frameborder="0"
+          src={src}
+        ></iframe>
+        {isLoaded.value && (
+          <div
+            style={{
+              display: props.activeModel ? '' : 'none'
+            }}
+            class={styles.startBtn}
+            onClick={(e: Event) => {
+              e.stopPropagation()
+              gotoAccomany()
+            }}
+          >
+            <img src={iconStart} />
+          </div>
+        )}
+        <div class={styles.skeletonWrap}>
+          <Skeleton class={styles.skeleton} row={8} />
+        </div>
+      </div>
+    )
+  }
+})

+ 110 - 0
src/views/courseware-play/component/point.module.less

@@ -0,0 +1,110 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  min-width: 288px;
+  max-width: 288px;
+  height: 100vh;
+  color: #333;
+  font-size: 12px;
+  box-sizing: border-box;
+  background:#fff;
+}
+.pointHead {
+  display: flex;
+  align-items: center;
+  padding: 13px 10px 15px 15px;
+  flex-shrink: 0;
+  font-size: 14px;
+  img {
+    width: 16px;
+    height: 16px;
+    margin-right: 7px;
+  }
+}
+.content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 0 20px;
+}
+.collapse {
+  &.childActive {
+    :global {
+      .van-cell {
+        color: #fff;
+      }
+    }
+    .arrow {
+      opacity: 1;
+    }
+  }
+  &:after {
+    display: none;
+    border-width: 0;
+  }
+
+  .borderTop {
+    border-top: 1px solid rgba(255, 255, 255, 0.2);
+  }
+  :global {
+    .van-cell {
+      background: transparent;
+      font-size: 14px;
+      color: rgba(255, 255, 255, 0.5);
+      padding: 6px 0;
+    }
+    .van-cell__title {
+      font-weight: 600;
+    }
+    .van-collapse-item__title--expanded {
+      color: #fff;
+    }
+    .van-collapse-item--border:after {
+      display: none;
+    }
+    .van-collapse-item__content {
+      background: transparent;
+      color: #fff;
+      font-size: 10px;
+      padding: 0 0 6px;
+    }
+  }
+  .arrow {
+    display: block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    transition: all 0.3s;
+    opacity: 0.5;
+  }
+}
+.item {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  padding: 6px 11px;
+  border-radius: 6px;
+  font-size: 12px;
+  :global {
+    .van-icon {
+      display: none;
+    }
+    .van-image {
+      margin-right: 6px;
+      margin-top: -1px;
+      width: 15px;
+      height: 16px;
+    }
+  }
+}
+.name{
+  flex: 1;
+}
+.itemActive {
+  background: #E0E0E0;
+  color: var(--van-primary);
+  :global {
+    .van-icon {
+      display: block;
+    }
+  }
+}

+ 102 - 0
src/views/courseware-play/component/points.tsx

@@ -0,0 +1,102 @@
+import { defineComponent, reactive, watch } from 'vue';
+import styles from './point.module.less';
+import iconMulv from '../image/icon-mulv.svg';
+import iconArrow from '../image/icon-arrow.svg';
+import iconZhibo from '../image/icon-load.gif';
+import iconImage from '../image/icon-image.svg';
+import iconImageActive from '../image/icon-image-active.svg';
+import iconVideo from '../image/icon-video.svg';
+import iconVideoActive from '../image/icon-video-active.svg';
+import iconSong from '../image/icon-song.svg';
+import iconSongActive from '../image/icon-song-active.svg';
+import { Collapse, Icon, Image } from 'vant';
+export default defineComponent({
+  name: 'points',
+  props: {
+    data: {
+      type: Array,
+      default: () => []
+    },
+    tabActive: {
+      type: String,
+      default: ''
+    },
+    itemActive: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['handleSelect'],
+  setup(props, { emit }) {
+    const pointData = reactive({
+      active: props.tabActive[0] || '',
+      childActive: props.tabActive[1] || ''
+    });
+    watch(
+      () => props.tabActive,
+      () => {
+        pointData.active = props.tabActive[0] || '';
+        pointData.childActive = props.tabActive[1] || '';
+      }
+    );
+
+    console.log(
+      pointData.active,
+      'pointData.active',
+      props.data,
+      props.tabActive
+    );
+
+    // 获取对应图片
+    const getImage = (item: any) => {
+      if (item.type === 'VIDEO') {
+        return props.itemActive == item.id ? iconVideoActive : iconVideo;
+      } else if (['IMAGE', 'IMG'].includes(item.type)) {
+        return props.itemActive == item.id ? iconImageActive : iconImage;
+      } else if (item.type === 'SONG') {
+        return props.itemActive == item.id ? iconSongActive : iconSong;
+      } else {
+        return props.itemActive == item.id ? iconVideoActive : iconVideo;
+      }
+    };
+    return () => (
+      <div class={styles.container}>
+        <div class={styles.pointHead}>
+          <img src={iconMulv} />
+          课件列表
+        </div>
+        <div class={styles.content}>
+          <Collapse
+            class={styles.collapse}
+            modelValue={pointData.active}
+            onUpdate:modelValue={(val: any) => {
+              pointData.active = val;
+            }}
+            accordion>
+            {props.data.map((item: any, index: number) => {
+              return (
+                <div
+                  class={[
+                    styles.item,
+                    props.itemActive == item.id ? styles.itemActive : ''
+                  ]}
+                  onClick={() => {
+                    emit('handleSelect', {
+                      itemActive: item.id,
+                      tabName: item.name
+                    });
+                  }}>
+                  <Image src={getImage(item)} class={styles.itemImage} />
+                  <span class={["van-ellipsis", styles.name]}>
+                    {item.name}
+                  </span>
+                  <Icon name={iconZhibo} />
+                </div>
+              );
+            })}
+          </Collapse>
+        </div>
+      </div>
+    );
+  }
+});

+ 32 - 0
src/views/courseware-play/component/tool.module.less

@@ -0,0 +1,32 @@
+.tool {
+    position: relative;
+    width: 220px;
+    box-sizing: border-box;
+    height: 100vh;
+    color: #fff;
+    font-size: 13px;
+    font-weight: 500;
+    line-height: 18px;
+    * {
+        box-sizing: border-box;
+    }
+}
+
+.title {
+    padding: 12px 18px;
+}
+.grid{
+    :global{
+        .van-grid-item__content{
+            background: transparent;
+            padding: var(--van-padding-xs) var(--van-padding-xs);
+        }
+        .van-grid-item__text{
+            color: inherit;
+            margin-top: 2px;
+        }
+        .van-grid-item__icon{
+            font-size: 22px;
+        }
+    }
+}

+ 40 - 0
src/views/courseware-play/component/tool.tsx

@@ -0,0 +1,40 @@
+import { Grid, GridItem } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './tool.module.less'
+import iconPen from '../image/icon-pen.png'
+
+export type ToolType =  'init' | 'pen'
+
+export type ToolItem = {
+    type: ToolType
+    name: string
+    icon: string
+}
+
+export default defineComponent({
+  name: 'tool',
+  emits: ['handleTool'],
+  setup(props, { emit }) {
+    const tool: ToolItem[] = [
+      {
+        type: 'pen',
+        icon: iconPen,
+        name: '批注'
+      }
+    ]
+    return () => (
+      <div class={styles.tool}>
+        <div class={styles.title}>教学功能</div>
+        <Grid class={styles.grid} columnNum={3} border={false}>
+          {tool.map((item) => (
+            <GridItem
+              icon={item.icon}
+              text={item.name}
+              onClick={() => emit('handleTool', item)}
+            ></GridItem>
+          ))}
+        </Grid>
+      </div>
+    )
+  }
+})

+ 43 - 0
src/views/courseware-play/component/tools/pen.module.less

@@ -0,0 +1,43 @@
+.pen{
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    top: 0;
+    z-index: 11;
+}
+.open{
+    display: block;
+}
+.hide{
+    display: none;
+}
+.iframe{
+    display: block;
+    width: 100%;
+    height: 100%;
+    border: 0;
+}
+.dely{
+    opacity: 0;
+}
+.rightItem{
+    position: absolute;
+    right: 15Px;
+    bottom: 0;
+    bottom: constant(safe-area-inset-bottom);
+    bottom: env(safe-area-inset-bottom);
+    width: 50Px;
+    height: 54Px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.img{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    display: block;
+}

+ 141 - 0
src/views/courseware-play/component/tools/pen.tsx

@@ -0,0 +1,141 @@
+import { promisefiyPostMessage } from '@/helpers/native-message'
+import html2canvas from 'html2canvas'
+import { closeToast, Icon, showFailToast, showLoadingToast, showSuccessToast } from 'vant'
+import { defineComponent, toRefs, ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
+import styles from './pen.module.less'
+
+export default defineComponent({
+  name: 'pen',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    close: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  setup(props) {
+    const { show } = toRefs(props)
+    const firstRender = ref(true)
+    const src = /(localhost|192)/.test(location.host)
+      ? 'https://test.lexiaoya.cn/whiteboard-noCollab'
+      : `${location.origin}/whiteboard-noCollab`
+
+    const exportImg = (event: MessageEvent) => {
+      const data = event.data
+      // console.log('🚀 ~ event:', data)
+      if (data.api === 'excalidraw_exportImg') {
+        imgs.base64 = data.base64
+        imgs.exported = true
+        nextTick(() => {
+          onSaveImg()
+        })
+      }
+    }
+    onMounted(() => {
+      window.addEventListener('message', exportImg)
+    })
+    onUnmounted(() => {
+      window.removeEventListener('message', exportImg)
+    })
+
+    const imgs = reactive({
+      exported: false,
+      saveLoading: false,
+      base64: '',
+      image: ''
+    })
+
+    const saveImg = async () => {
+      showLoadingToast({ message: '图片生成中...', forbidClick: true })
+      setTimeout(() => {
+        imgs.saveLoading = false
+      }, 100)
+      const res = await promisefiyPostMessage({
+        api: 'savePicture',
+        content: {
+          base64: imgs.image
+        }
+      })
+      if (res?.content?.status === 'success') {
+        showSuccessToast('保存成功')
+      } else {
+        showFailToast('保存失败')
+      }
+      imgs.exported = false
+    }
+
+    const onSaveImg = async () => {
+      // 判断是否在保存中...
+      if (imgs.saveLoading) {
+        return
+      }
+      console.log('开始')
+      imgs.saveLoading = true
+      const container: any = document.getElementById(`app`)
+      html2canvas(container, {
+        allowTaint: true,
+        useCORS: true,
+        backgroundColor: null
+      })
+        .then(async (canvas) => {
+          // console.log("🚀 ~ canvas:", canvas)
+          // document.body.appendChild(canvas)
+          // const url = await canvas.toDataURL()
+          try {
+            imgs.image = canvas.toDataURL()
+            
+          } catch (error) {
+            console.log(error)
+          }
+          console.log("🚀 ~ imgs.image:", imgs.image)
+          saveImg()
+        })
+        .catch((error) => {
+          console.log("🚀 ~ error:", error)
+          closeToast()
+          imgs.saveLoading = false
+          imgs.exported = false
+        })
+    }
+
+    return () => (
+      <div
+        class={[
+          styles.pen,
+          firstRender.value ? styles.dely : '',
+          show.value ? styles.open : styles.hide
+        ]}
+      >
+        <iframe
+          class={styles.iframe}
+          frameborder="0"
+          width="100vw"
+          height="100vh"
+          src={src}
+          onLoad={() => {
+            firstRender.value = false
+          }}
+        ></iframe>
+        {imgs.exported ? (
+          <img crossorigin="anonymous" class={styles.img} src={imgs.base64} />
+        ) : (
+          <div
+            class={styles.rightItem}
+            onClick={() => props.close()}
+          >
+            <svg width="22px" height="20px" viewBox="0 0 22 20">
+              <path
+                transform="translate(-1.000000, -2.000000)"
+                fill="#FFFFFF"
+                d="M13,2 C13.5522847,2 14,2.44771525 14,3 C14,3.51283584 13.6139598,3.93550716 13.1166211,3.99327227 L13,4 L3,4 L3,20 L13,20 C13.5128358,20 13.9355072,20.3860402 13.9932723,20.8833789 L14,21 C14,21.5128358 13.6139598,21.9355072 13.1166211,21.9932723 L13,22 L2,22 C1.48716416,22 1.06449284,21.6139598 1.00672773,21.1166211 L1,21 L1,3 C1,2.48716416 1.38604019,2.06449284 1.88337887,2.00672773 L2,2 L13,2 Z M17.7071068,7.05025253 L21.9497475,11.2928932 L21.9497475,11.2928932 C22.3402718,11.6834175 22.3402718,12.3165825 21.9497475,12.7071068 L17.7071068,16.9497475 C17.3165825,17.3402718 16.6834175,17.3402718 16.2928932,16.9497475 C15.9023689,16.5592232 15.9023689,15.9260582 16.2928932,15.5355339 L18.828,12.999 L9.29368112,13 C8.74139637,13 8.29368112,12.5522847 8.29368112,12 C8.29368112,11.4871642 8.67972131,11.0644928 9.17706,11.0067277 L9.29368112,11 L18.827,10.999 L16.2928932,8.46446609 C15.9023689,8.0739418 15.9023689,7.44077682 16.2928932,7.05025253 C16.6834175,6.65972824 17.3165825,6.65972824 17.7071068,7.05025253 Z"
+              ></path>
+            </svg>
+          </div>
+        )}
+      </div>
+    )
+  }
+})

+ 164 - 0
src/views/courseware-play/component/video-play.tsx

@@ -0,0 +1,164 @@
+import { defineComponent, nextTick, onMounted, toRefs } from 'vue'
+import 'plyr/dist/plyr.css'
+import Plyr from 'plyr'
+import { ref } from 'vue'
+import styles from './video.module.less'
+
+import iconLoop from '../image/icon-loop.svg'
+import iconLoopActive from '../image/icon-loop-active.svg'
+import iconplay from '../image/icon-play.svg'
+import iconpause from '../image/icon-pause.svg'
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    isEmtry: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
+  setup(props, { emit, expose }) {
+    const { item, isEmtry } = toRefs(props)
+    const videoRef = ref()
+    const videoItem = ref<Plyr>()
+    const controlID = 'v' + Date.now() + Math.floor(Math.random() * 100)
+    const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 100)
+    const loopBtnId = 'loop' + Date.now() + Math.floor(Math.random() * 100)
+    const toggleHideControl = (isShow: false) => {
+      videoItem.value?.toggleControls(isShow)
+    }
+    const togglePlay = (e: Event) => {
+      e.stopPropagation()
+      videoItem.value?.togglePlay()
+    }
+    const toggleLoop = (e: Event) => {
+      const loopBtn = document.getElementById(loopBtnId)
+      if (!loopBtn || !videoItem.value) return
+      const isLoop = videoItem.value.loop
+      if (isLoop) {
+        loopBtn.classList.remove(styles.active)
+      } else {
+        loopBtn.classList.add(styles.active)
+      }
+      videoItem.value.loop = !videoItem.value.loop
+    }
+    const onDefault = () => {
+      document.getElementById(controlID)?.addEventListener('click', (e: Event) => {
+        e.stopPropagation()
+        emit('reset')
+      })
+      document.getElementById(playBtnId)?.addEventListener('click', togglePlay)
+      document.getElementById(loopBtnId)?.addEventListener('click', toggleLoop)
+    }
+
+    const changePlayBtn = (code: string) => {
+      const playBtn = document.getElementById(playBtnId)
+      if (!playBtn) return
+      if (code == 'play') {
+        playBtn.classList.remove(styles.btnPause)
+        playBtn.classList.add(styles.btnPlay)
+      } else {
+        playBtn.classList.remove(styles.btnPlay)
+        playBtn.classList.add(styles.btnPause)
+      }
+    }
+    const controls = `
+            <div id="${controlID}" class="plyr__controls bottomFixed ${styles.controls}">
+                <div class="${styles.time}">
+                    <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
+                    <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
+                </div>
+                <div class="${styles.slider}">
+                    <div class="plyr__progress">
+                        <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
+                        <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
+                        <span role="tooltip" class="plyr__tooltip">00:00</span>
+                    </div>
+                </div>
+                <div class="${styles.actions}">
+                    <div class="${styles.actionWrap}">
+                        <button id="${playBtnId}" class="${styles.actionBtn}">
+                            <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>  
+                            <img class="${styles.playIcon}" src="${iconplay}" />
+                            <img class="${styles.playIcon}" src="${iconpause}" />
+                        </button>
+                        <button id="${loopBtnId}" class="${styles.actionBtn} ${styles.loopBtn}">
+                            <img class="loop" src="${iconLoop}" />
+                            <img class="loopActive" src="${iconLoopActive}" />
+                        </button>
+                    </div>
+                    <div>${item.value.name}</div>
+                </div>
+            </div>`
+
+    onMounted(() => {
+      videoItem.value = new Plyr(videoRef.value, {
+        autoplay: true,
+        controls: controls,
+        autopause: true, // 一次只允许
+        ratio: '16:9', // 强制所有视频的纵横比
+        hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
+        clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
+        fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
+      })
+      if (videoItem.value) {
+        videoItem.value.on('play', () => {
+          if (videoItem.value) {
+            videoItem.value.muted = false
+            videoItem.value.volume = 1
+          }
+            
+          // console.log('开始播放', item.value)
+          if (!item.value.autoPlay && !item.value.isprepare && videoItem.value) {
+            // 加载完成后,取消静音播放
+            
+            console.log(videoItem.value)
+            videoItem.value.pause()
+          }
+          changePlayBtn('')
+          emit('togglePlay', videoItem.value?.paused)
+        })
+        videoItem.value.on('pause', () => {
+          changePlayBtn('play')
+          emit('togglePlay', videoItem.value?.paused)
+        })
+        videoItem.value.on('ended', (e: Event) => {
+          emit('ended')
+          changePlayBtn('play')
+        })
+        videoItem.value.once('loadedmetadata', (e: Event) => {
+          changePlayBtn('play')
+          if (item.value.autoPlay && videoItem.value) {
+            videoItem.value.play()
+          }
+          emit('loadedmetadata', videoItem.value)
+        })
+
+        nextTick(() => {
+          onDefault()
+        })
+      }
+    })
+    expose({
+      changePlayBtn,
+      toggleHideControl
+    })
+    return () => (
+      <div class={styles.videoWrap}>
+        <video
+          style={{ width: '100%', height: '100%' }}
+          src={isEmtry.value ? '' : item.value.content}
+          ref={videoRef}
+          playsinline="false"
+        ></video>
+      </div>
+    )
+  }
+})

+ 133 - 0
src/views/courseware-play/component/video.module.less

@@ -0,0 +1,133 @@
+.videoWrap {
+    width: 100%;
+    height: 100%;
+
+    :global {
+        .plyr--video {
+            width: 100%;
+            height: 100%;
+        }
+
+        .plyr__time {
+            display: block !important;
+        }
+        .plyr__video-wrapper{
+            pointer-events: none;
+        }
+    }
+}
+
+:global(.bottomFixed).controls {
+    width: 100%;
+    background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+    padding: 0 !important;
+    flex-direction: column;
+    transition: all 0.5s;
+
+    .time {
+        display: flex;
+        justify-content: space-between;
+        width: 100%;
+        color: #fff;
+        font-size: 10px;
+        padding: 4px 20px;
+
+        :global {
+            .plyr__time+.plyr__time:before {
+                content: '';
+            }
+        }
+    }
+
+    .slider {
+        width: 100%;
+        padding: 0 20px;
+
+        :global {
+            .van-slider__button {
+                background: var(--van-primary);
+            }
+
+            .van-loading {
+                width: 100%;
+                height: 100%;
+            }
+        }
+    }
+
+    .actions {
+        display: flex;
+        justify-content: space-between;
+        width: 100%;
+        color: #fff;
+        font-size: 12px;
+        padding: 0 20px;
+        align-items: center;
+
+        .actionWrap {
+            display: flex;
+        }
+
+        .actionBtn {
+            display: flex;
+            width: 38px;
+            height: 38px;
+            padding: 4px 0;
+            background: transparent;
+        }
+
+        .actionBtn>img {
+            width: 100%;
+            height: 100%;
+        }
+
+        :global {
+            .van-loading__circular {
+                width: 100%;
+                height: 100%;
+            }
+        }
+
+        .playIcon {
+            display: none;
+        }
+
+        .btnPlay img:nth-child(2) {
+            display: block;
+        }
+
+        .btnPause img:nth-child(3) {
+            display: block;
+        }
+
+        .btnPlay,
+        .btnPause {
+            :global {
+                .van-loading {
+                    display: none;
+                }
+            }
+        }
+        .loopBtn{
+            :global{
+                .loop{
+                    display: block;
+                }
+                .loopActive{
+                    display: none;
+                }
+            }
+        }
+        .loopBtn.active{
+            :global{
+                .loop{
+                    display: none;
+                }
+                .loopActive{
+                    display: block;
+                }
+            }
+        }
+
+    }
+}

File diff suppressed because it is too large
+ 0 - 0
src/views/courseware-play/datas/data.json


+ 11 - 0
src/views/courseware-play/image/back.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="19px" height="19px" viewBox="0 0 19 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g id="课件播放(老师)" transform="translate(-40.000000, -13.000000)" stroke="#FFFFFF" stroke-width="2">
+            <g id="图标/通用/返回" transform="translate(40.000000, 13.000000)">
+                <polyline id="Stroke-1" points="13.5 18 5 9.5 13.5 1"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>

+ 15 - 0
src/views/courseware-play/image/icon-arrow.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放目录(老师)" transform="translate(-570.000000, -213.000000)" fill="#FFFFFF">
+            <g id="编组" transform="translate(548.000000, 0.000000)">
+                <g id="编组-5" transform="translate(22.000000, 209.000000)">
+                    <g id="展开更多备份" transform="translate(6.000000, 10.000000) rotate(-90.000000) translate(-6.000000, -10.000000) translate(0.000000, 4.000000)">
+                        <path d="M6.85328183,4.39627936 L10.070225,9.66036817 C10.3582139,10.1316227 10.2096477,10.7471111 9.73839317,11.0351 C9.58138526,11.1310493 9.40094791,11.1818182 9.21694316,11.1818182 L2.78305684,11.1818182 C2.23077209,11.1818182 1.78305684,10.7341029 1.78305684,10.1818182 C1.78305684,9.99781343 1.83382572,9.81737609 1.92977501,9.66036817 L5.14671817,4.39627936 C5.43470705,3.92502482 6.05019547,3.77645865 6.52145001,4.06444754 C6.6568159,4.14717114 6.77055823,4.26091347 6.85328183,4.39627936 Z" id="展开更多" transform="translate(6.000000, 7.090909) rotate(-180.000000) translate(-6.000000, -7.090909) "></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 24 - 0
src/views/courseware-play/image/icon-dian.svg

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M23.21608,16.0687402 L23.3033009,16.1464466 C23.6659306,16.5090763 23.6918327,17.0809217 23.3810072,17.4734394 L23.3033009,17.5606602 L17.6464466,23.2175144 C17.2838169,23.5801441 16.7119715,23.6060462 16.3194539,23.2952208 L16.232233,23.2175144 L12.6966991,19.6819805 C12.3061748,19.2914562 12.3061748,18.6582912 12.6966991,18.267767 C13.0593288,17.9051373 13.6311742,17.8792351 14.0236919,18.1900606 L14.1109127,18.267767 L16.9384817,21.0967502 L21.8890873,16.1464466 C22.251717,15.7838169 22.8235624,15.7579148 23.21608,16.0687402 Z M11,0 C14.8659932,0 18,3.13400675 18,7 C18,9.41274397 16.7793245,11.5403839 14.9218582,12.7990351 C15.9323681,13.229421 16.8689386,13.8268917 17.694678,14.5714883 C18.1048344,14.9413398 18.1375079,15.5736612 17.7676564,15.9838176 C17.3978048,16.393974 16.7654834,16.4266475 16.355327,16.0567959 C14.8954225,14.7403517 13.0066893,14 11,14 C6.581722,14 3,17.581722 3,22 C3,22.5522847 2.55228475,23 2,23 C1.44771525,23 1,22.5522847 1,22 C1,17.8693735 3.50442274,14.3236863 7.07765565,12.7985509 C5.22023262,11.5396119 4,9.41230625 4,7 C4,3.13400675 7.13400675,0 11,0 Z M11,2 C8.23857625,2 6,4.23857625 6,7 C6,9.76142375 8.23857625,12 11,12 C13.7614237,12 16,9.76142375 16,7 C16,4.23857625 13.7614237,2 11,2 Z" id="path-1"></path>
+        <filter x="-13.3%" y="-12.8%" width="126.6%" height="125.5%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.125792177 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(老师)" transform="translate(-740.000000, -151.000000)" fill-rule="nonzero">
+            <g id="编组-8" transform="translate(732.000000, 66.000000)">
+                <g id="编组-7" transform="translate(0.000000, 75.000000)">
+                    <g id="形状结合" transform="translate(8.000000, 10.000000)">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                        <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 22 - 0
src/views/courseware-play/image/icon-down.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M12.619886,7.97254617 L12.7071068,8.05025253 L16.2426407,11.5857864 C16.633165,11.9763107 16.633165,12.6094757 16.2426407,13 C15.880011,13.3626297 15.3081656,13.3885318 14.9156479,13.0777064 L14.8284271,13 L12.9994661,11.1713593 L13,19 C13,19.5522847 12.5522847,20 12,20 C11.4477153,20 11,19.5522847 11,19 L10.9994661,11.1703593 L9.17157288,13 C8.80894318,13.3626297 8.23709778,13.3885318 7.84458013,13.0777064 L7.75735931,13 C7.39472961,12.6373703 7.36882749,12.0655249 7.67965295,11.6730073 L7.75735931,11.5857864 L11.2928932,8.05025253 C11.6555229,7.68762283 12.2273683,7.66172071 12.619886,7.97254617 Z M18.363961,5.63603897 C21.8786797,9.15075759 21.8786797,14.8492424 18.363961,18.363961 C17.9734367,18.7544853 17.3402718,18.7544853 16.9497475,18.363961 C16.5592232,17.9734367 16.5592232,17.3402718 16.9497475,16.9497475 C19.6834175,14.2160774 19.6834175,9.78392257 16.9497475,7.05025253 C14.2160774,4.31658249 9.78392257,4.31658249 7.05025253,7.05025253 C4.31658249,9.78392257 4.31658249,14.2160774 7.05025253,16.9497475 C7.44077682,17.3402718 7.44077682,17.9734367 7.05025253,18.363961 C6.65972824,18.7544853 6.02656326,18.7544853 5.63603897,18.363961 C2.12132034,14.8492424 2.12132034,9.15075759 5.63603897,5.63603897 C9.15075759,2.12132034 14.8492424,2.12132034 18.363961,5.63603897 Z" id="path-1"></path>
+        <filter x="-33.3%" y="-35.3%" width="166.7%" height="170.6%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放/全屏(伴学端)" transform="translate(-90.000000, -227.000000)" fill-rule="nonzero">
+            <g id="编组-8备份" transform="translate(82.000000, 68.000000)">
+                <g id="形状结合" transform="translate(20.000000, 171.000000) rotate(-180.000000) translate(-20.000000, -171.000000) translate(8.000000, 159.000000)">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 19 - 0
src/views/courseware-play/image/icon-image-active.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(三层)" transform="translate(-306.000000, -298.000000)" fill="#FF8057" fill-rule="nonzero">
+            <g id="编组" transform="translate(306.000000, 0.000000)">
+                <g id="编组-8" transform="translate(0.000000, 93.500000)">
+                    <g id="编组-12" transform="translate(0.000000, 65.000000)">
+                        <g id="知识点目录/图片备份" transform="translate(0.000000, 140.000000)">
+                            <g id="编组" transform="translate(7.500000, 7.500000) scale(-1, 1) translate(-7.500000, -7.500000) translate(0.500000, 2.000000)">
+                                <path d="M12.6293706,0 C13.3863483,-1.39054547e-16 14,0.613651672 14,1.37062937 L14,1.37062937 L14,9.62937063 C14,10.3863483 13.3863483,11 12.6293706,11 L12.6293706,11 L1.37062937,11 C0.613651672,11 9.27030316e-17,10.3863483 0,9.62937063 L0,9.62937063 L0,1.37062937 C-9.27030316e-17,0.613651672 0.613651672,1.39054547e-16 1.37062937,0 L1.37062937,0 Z M9.22142099,4.12294163 C8.89196248,3.93663301 8.47385062,4.05267893 8.287542,4.38213744 L8.287542,4.38213744 L7.04470782,6.57990105 C6.98353166,6.68808179 6.89408921,6.77759053 6.78595381,6.8388468 C6.4566334,7.02539942 6.03843569,6.90966326 5.85188307,6.58034285 L5.85188307,6.58034285 L5.64657316,6.21791028 C5.58509397,6.10938138 5.49516084,6.01968934 5.3864673,5.95850172 C5.05664729,5.77283381 4.63876177,5.88969218 4.45309386,6.21951219 L4.45309386,6.21951219 L3.17297968,8.49350383 C3.11520712,8.59613088 3.08485695,8.71191444 3.08485695,8.82968531 C3.08485695,9.20817416 3.39168279,9.515 3.77017164,9.515 L3.77017164,9.515 L11.2094939,9.515 C11.3277631,9.515 11.4440194,9.4843926 11.5469559,9.42615461 C11.8763768,9.23977951 11.9923383,8.82164423 11.8059632,8.49222333 L11.8059632,8.49222333 L9.4805487,4.38201704 C9.41928897,4.27373951 9.32971088,4.18417951 9.22142099,4.12294163 Z M3.23076923,1.5125 C2.34230769,1.5125 1.61538462,2.255 1.61538462,3.1625 C1.61538462,4.07 2.34230769,4.8125 3.23076923,4.8125 C4.11923077,4.8125 4.84615385,4.07 4.84615385,3.1625 C4.84615385,2.255 4.11923077,1.5125 3.23076923,1.5125 Z" id="形状结合"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 19 - 0
src/views/courseware-play/image/icon-image.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(三层)" transform="translate(-306.000000, -268.000000)" fill="#FFFFFF" fill-rule="nonzero">
+            <g id="编组" transform="translate(306.000000, 0.000000)">
+                <g id="编组-8" transform="translate(0.000000, 93.500000)">
+                    <g id="编组-12" transform="translate(0.000000, 65.000000)">
+                        <g id="知识点目录/图片备份-4" transform="translate(0.000000, 110.000000)">
+                            <g id="编组" transform="translate(7.500000, 7.500000) scale(-1, 1) translate(-7.500000, -7.500000) translate(0.500000, 2.000000)">
+                                <path d="M12.6293706,0 C13.3863483,-1.39054547e-16 14,0.613651672 14,1.37062937 L14,1.37062937 L14,9.62937063 C14,10.3863483 13.3863483,11 12.6293706,11 L12.6293706,11 L1.37062937,11 C0.613651672,11 9.27030316e-17,10.3863483 0,9.62937063 L0,9.62937063 L0,1.37062937 C-9.27030316e-17,0.613651672 0.613651672,1.39054547e-16 1.37062937,0 L1.37062937,0 Z M9.22142099,4.12294163 C8.89196248,3.93663301 8.47385062,4.05267893 8.287542,4.38213744 L8.287542,4.38213744 L7.04470782,6.57990105 C6.98353166,6.68808179 6.89408921,6.77759053 6.78595381,6.8388468 C6.4566334,7.02539942 6.03843569,6.90966326 5.85188307,6.58034285 L5.85188307,6.58034285 L5.64657316,6.21791028 C5.58509397,6.10938138 5.49516084,6.01968934 5.3864673,5.95850172 C5.05664729,5.77283381 4.63876177,5.88969218 4.45309386,6.21951219 L4.45309386,6.21951219 L3.17297968,8.49350383 C3.11520712,8.59613088 3.08485695,8.71191444 3.08485695,8.82968531 C3.08485695,9.20817416 3.39168279,9.515 3.77017164,9.515 L3.77017164,9.515 L11.2094939,9.515 C11.3277631,9.515 11.4440194,9.4843926 11.5469559,9.42615461 C11.8763768,9.23977951 11.9923383,8.82164423 11.8059632,8.49222333 L11.8059632,8.49222333 L9.4805487,4.38201704 C9.41928897,4.27373951 9.32971088,4.18417951 9.22142099,4.12294163 Z M3.23076923,1.5125 C2.34230769,1.5125 1.61538462,2.255 1.61538462,3.1625 C1.61538462,4.07 2.34230769,4.8125 3.23076923,4.8125 C4.11923077,4.8125 4.84615385,4.07 4.84615385,3.1625 C4.84615385,2.255 4.11923077,1.5125 3.23076923,1.5125 Z" id="形状结合"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/views/courseware-play/image/icon-load.gif


+ 23 - 0
src/views/courseware-play/image/icon-loop-active.svg

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M5.00480947,15.25 C6.49624217,17.8332372 9.29235844,19.1935866 12.0802733,18.9792617 C12.1933752,19.672858 12.4261755,20.3264161 12.7562805,20.918295 C9.05647623,21.4111568 5.25493468,19.6832296 3.27275866,16.25 C2.99661629,15.7717074 3.16049144,15.160117 3.63878407,14.8839746 C4.11707669,14.6078322 4.7286671,14.7717074 5.00480947,15.25 Z M10.4132849,7.08904722 L10.5144958,7.14250707 L15.5144958,10.1425071 C16.123756,10.5080632 16.1595948,11.3606179 15.6220123,11.7834679 L15.5144958,11.8574929 L10.5144958,14.8574929 C9.88129747,15.2374119 9.08434351,14.8231057 9.00623111,14.1142925 L9,14 L9,8 C9,7.26157025 9.76529405,6.79134664 10.4132849,7.08904722 Z M19.7272413,6.75 C20.8183076,8.63978222 21.1863003,10.7486105 20.9170607,12.7561204 C20.3245684,12.4254359 19.6711964,12.1929305 18.9778301,12.0792818 C19.0927198,10.6209049 18.7822682,9.11325853 17.9951905,7.75 C15.9241227,4.16280532 11.3371947,2.93374166 7.75,5.00480947 C6.85788041,5.51987496 6.09738693,6.19866524 5.49784407,6.99904787 L7,7 C7.51283584,7 7.93550716,7.38604019 7.99327227,7.88337887 L8,8 C8,8.51283584 7.61395981,8.93550716 7.11662113,8.99327227 L7,9 L3,9 C2.48716416,9 2.06449284,8.61395981 2.00672773,8.11662113 L2,8 L2,4 C2,3.44771525 2.44771525,3 3,3 C3.51283584,3 3.93550716,3.38604019 3.99327227,3.88337887 L4,4 L3.99919549,5.66623947 C4.74283972,4.70951337 5.67119653,3.89560614 6.75,3.27275866 C11.2937799,0.649406102 17.1038888,2.20622008 19.7272413,6.75 Z M11.001,9.766 L11.001,12.233 L13.057,11 L11.001,9.766 Z" id="path-1"></path>
+        <filter x="-31.6%" y="-31.6%" width="163.2%" height="163.1%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(控件)" transform="translate(-42.000000, -289.000000)" fill-rule="nonzero">
+            <g id="循环备份" transform="translate(42.000000, 289.000000)">
+                <g id="形状结合">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+                <path d="M18,13 C20.7614237,13 23,15.2385763 23,18 C23,20.7614237 20.7614237,23 18,23 C15.2385763,23 13,20.7614237 13,18 C13,15.2385763 15.2385763,13 18,13 Z M20.9553274,16.1420886 C20.7524108,15.9832858 20.4506336,16.0042922 20.2548135,16.2001122 L20.2548135,16.2001122 L17.5814904,18.87328 L16.1273302,17.4183562 L16.0823515,17.3785153 C15.8794349,17.2197125 15.5776577,17.2407189 15.3818377,17.436539 C15.1709545,17.6474221 15.1628139,17.9811905 15.3636549,18.1820315 L15.3636549,18.1820315 L17.1819295,20.0003061 L17.2269082,20.0401471 C17.4298248,20.1989498 17.731602,20.1779434 17.9274221,19.9821234 L17.9274221,19.9821234 L20.9821234,16.9274221 L21.0242071,16.8804452 C21.1931032,16.669536 21.1868014,16.3684248 21.0003061,16.1819295 L21.0003061,16.1819295 Z" id="形状结合" fill="#FF8057"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 20 - 0
src/views/courseware-play/image/icon-loop.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M19.7272413,6.75 C22.3505939,11.2937799 20.7937799,17.1038888 16.25,19.7272413 C11.7062201,22.3505939 5.89611123,20.7937799 3.27275866,16.25 C2.99661629,15.7717074 3.16049144,15.160117 3.63878407,14.8839746 C4.11707669,14.6078322 4.7286671,14.7717074 5.00480947,15.25 C7.07587728,18.8371947 11.6628053,20.0662583 15.25,17.9951905 C18.8371947,15.9241227 20.0662583,11.3371947 17.9951905,7.75 C15.9241227,4.16280532 11.3371947,2.93374166 7.75,5.00480947 C6.85788041,5.51987496 6.09738693,6.19866524 5.49784407,6.99904787 L7,7 C7.51283584,7 7.93550716,7.38604019 7.99327227,7.88337887 L8,8 C8,8.51283584 7.61395981,8.93550716 7.11662113,8.99327227 L7,9 L3,9 C2.48716416,9 2.06449284,8.61395981 2.00672773,8.11662113 L2,8 L2,4 C2,3.44771525 2.44771525,3 3,3 C3.51283584,3 3.93550716,3.38604019 3.99327227,3.88337887 L4,4 L3.99919549,5.66623947 C4.74283972,4.70951337 5.67119653,3.89560614 6.75,3.27275866 C11.2937799,0.649406102 17.1038888,2.20622008 19.7272413,6.75 Z M9,8 C9,7.22270552 9.84797124,6.74259237 10.5144958,7.14250707 L10.5144958,7.14250707 L15.5144958,10.1425071 C16.1618347,10.5309105 16.1618347,11.4690895 15.5144958,11.8574929 L15.5144958,11.8574929 L10.5144958,14.8574929 C9.84797124,15.2574076 9,14.7772945 9,14 L9,14 Z M11.001,9.766 L11.001,12.233 L13.057,11 L11.001,9.766 Z" id="path-1"></path>
+        <filter x="-31.6%" y="-31.6%" width="163.2%" height="163.1%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(控件)" transform="translate(-42.000000, -246.000000)" fill-rule="nonzero">
+            <g id="形状结合" transform="translate(42.000000, 246.000000)">
+                <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                <use fill="#FFFFFF" xlink:href="#path-1"></use>
+            </g>
+        </g>
+    </g>
+</svg>

+ 22 - 0
src/views/courseware-play/image/icon-menu.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M8,3.05555556 C8.55228475,3.05555556 9,3.50327081 9,4.05555556 C9,4.56839139 8.61395981,4.99106272 8.11662113,5.04882782 L8,5.05555556 L5,5.055 L5,18.166 L6.89382295,18.1650103 L7.8944779,18.1192618 L8.45463981,18.1009676 C8.97540753,18.0908139 9.39376478,18.1061033 9.85166087,18.1560296 C10.5988853,18.2375025 11.2716806,18.410537 11.869405,18.7012165 L11.962,18.75 L12.150716,18.648737 C12.7355414,18.3555152 13.410772,18.1951664 14.1825209,18.1316921 L14.4764629,18.11236 C14.6020344,18.1060784 14.7233647,18.1019545 14.8465386,18.0998874 L15.2276753,18.0997546 L15.6563812,18.1113547 L16.8123482,18.1630013 L17.0528496,18.1666667 L19,18.166 L19,5.055 L16,5.05555556 C15.0757983,5.05555556 14.1212204,5.4250449 13.1264033,6.1961916 L13,6.297 L13,12.5 C13,13.6045695 12.1045695,14.5 11,14.5 C9.8954305,14.5 9,13.6045695 9,12.5 C9,11.3954305 9.8954305,10.5 11,10.5 L11,5.83333333 C11,5.54769552 11.1221478,5.27569145 11.3356362,5.08592401 C12.7513965,3.82747041 14.2164018,3.13934357 15.7180205,3.06274198 L16,3.05555556 L20,3.05555556 C20.5128358,3.05555556 20.9355072,3.44159575 20.9932723,3.93893443 L21,4.05555556 L21,19.1666667 C21,19.6795025 20.6139598,20.1021738 20.1166211,20.1599389 L20,20.1666667 L16.8419244,20.1646052 L15.6503056,20.1126382 L15.2994182,20.1015612 L14.9950331,20.0986076 L14.7146311,20.1039848 L14.5763867,20.1098622 C13.6914704,20.1541296 13.0645656,20.3407859 12.6594823,20.6961645 C12.3051448,21.007024 11.7818382,21.0282314 11.4035084,20.7470638 C10.9621903,20.4190844 10.3765613,20.2251149 9.63487747,20.1442461 C9.26699345,20.1041342 8.91529545,20.0919297 8.45867564,20.1015475 L7.96259642,20.1182141 L6.91166572,20.1652562 L4,20.1666667 C3.48716416,20.1666667 3.06449284,19.7806265 3.00672773,19.2832878 L3,19.1666667 L3,4.05555556 C3,3.54271972 3.38604019,3.12004839 3.88337887,3.06228329 L4,3.05555556 L8,3.05555556 Z" id="path-1"></path>
+        <filter x="-33.3%" y="-33.5%" width="166.7%" height="167.1%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放" transform="translate(-698.000000, -103.000000)" fill-rule="nonzero">
+            <g id="编组-5" transform="translate(689.000000, 100.000000)">
+                <g id="形状结合" transform="translate(9.000000, 3.000000)">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/views/courseware-play/image/icon-more.png


+ 28 - 0
src/views/courseware-play/image/icon-mulv.svg

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>课程目录</title>
+    <defs>
+        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
+            <stop stop-color="#FFB790" offset="0%"></stop>
+            <stop stop-color="#FF8057" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="50%" y1="26.807622%" x2="50%" y2="100%" id="linearGradient-2">
+            <stop stop-color="#ACD7FF" offset="0%"></stop>
+            <stop stop-color="#FFB469" offset="100%"></stop>
+        </linearGradient>
+        <path d="M0,10 C0.045380368,6.68544475 0.62101804,3.94093846 2.30411553,2.30411553 C3.94093846,0.62101804 6.68544475,0.045380368 10,0 C13.3145553,0.045380368 16.0590615,0.62101804 17.6958845,2.30411553 C19.378982,3.94093846 19.9546196,6.68544475 20,10 C19.9546196,13.3145553 19.378982,16.0590615 17.6958845,17.6958845 C16.0590615,19.378982 13.3145553,19.9546196 10,20 C6.68544475,19.9546196 3.94093846,19.378982 2.30411553,17.6958845 C0.62101804,16.0590615 0.045380368,13.3145553 0,10 Z" id="path-3"></path>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件列表" transform="translate(-542.000000, -16.000000)" fill-rule="nonzero">
+            <g id="编组" transform="translate(524.000000, 0.000000)">
+                <g id="课程目录" transform="translate(18.000000, 16.000000)">
+                    <g id="路径">
+                        <use fill="url(#linearGradient-1)" xlink:href="#path-3"></use>
+                        <use fill="url(#linearGradient-2)" xlink:href="#path-3"></use>
+                    </g>
+                    <path d="M14.0837809,7.49598367 L5.91643478,7.49598367 C5.51435536,7.49598367 5.18518519,7.14283647 5.18518519,6.7109548 C5.18518519,6.27930471 5.51413965,5.92592593 5.91643478,5.92592593 L14.0835652,5.92592593 C14.4856446,5.92592593 14.8148148,6.27907313 14.8148148,6.7109548 C14.8148148,7.14260489 14.4858604,7.49598367 14.0837809,7.49598367 L14.0837809,7.49598367 Z M14.0837809,11.1552835 L5.91643478,11.1552835 C5.51435536,11.1552835 5.18518519,10.8021363 5.18518519,10.3702546 C5.18518519,9.93860449 5.51413965,9.58522571 5.91643478,9.58522571 L14.0835652,9.58522571 C14.4856446,9.58522571 14.8148148,9.93837292 14.8148148,10.3702546 C14.8148148,10.8021363 14.4858604,11.1552835 14.0837809,11.1552835 L14.0837809,11.1552835 Z M9.11106797,14.8148148 L5.91643478,14.8148148 C5.51435536,14.8148148 5.18518519,14.4616676 5.18518519,14.0297859 C5.18518519,13.5981358 5.51413965,13.2447571 5.91643478,13.2447571 L9.11085226,13.2447571 C9.51293169,13.2447571 9.84210196,13.5979043 9.84210196,14.0297859 C9.84231757,14.4616676 9.51314739,14.8148148 9.11106797,14.8148148 L9.11106797,14.8148148 Z" id="形状" fill="#FFFFFF"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 32 - 0
src/views/courseware-play/image/icon-pause.svg

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <rect id="path-1" x="4" y="3" width="5" height="18" rx="2"></rect>
+        <filter x="-120.0%" y="-33.3%" width="340.0%" height="166.7%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+        <rect id="path-3" x="15" y="3" width="5" height="18" rx="2"></rect>
+        <filter x="-120.0%" y="-33.3%" width="340.0%" height="166.7%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(控件)" transform="translate(-42.000000, -46.000000)" fill-rule="nonzero">
+            <g id="播放" transform="translate(42.000000, 46.000000)">
+                <g id="矩形">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+                <g id="矩形备份">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-3"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/views/courseware-play/image/icon-pen.png


+ 22 - 0
src/views/courseware-play/image/icon-play.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M15.2349141,5.27497835 L22.6793243,18.2549758 C23.228861,19.2131423 22.8976011,20.4353773 21.9394345,20.9849141 C21.6365821,21.1586088 21.293537,21.25 20.9444103,21.25 L6.05558972,21.25 C4.95102022,21.25 4.05558972,20.3545695 4.05558972,19.25 C4.05558972,18.9008732 4.14698087,18.5578282 4.32067566,18.2549758 L11.7650859,5.27497835 C12.3146227,4.31681177 13.5368577,3.98555182 14.4950242,4.53508853 C14.8029434,4.7116892 15.0583134,4.96705923 15.2349141,5.27497835 Z" id="path-1"></path>
+        <filter x="-27.5%" y="-31.6%" width="155.1%" height="163.2%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(控件)" transform="translate(-42.000000, -83.000000)" fill-rule="nonzero">
+            <g id="播放备份" transform="translate(42.000000, 83.000000)">
+                <g id="三角形" transform="translate(13.500000, 11.750000) rotate(-270.000000) translate(-13.500000, -11.750000) ">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 24 - 0
src/views/courseware-play/image/icon-point.svg

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M17.9889353,7.03683462 C15.5779696,4.12723478 11.4245096,3.37164362 8.14331066,5.24573351 C4.86211176,7.1198234 3.40277969,11.0812175 4.68401757,14.6360727 C5.96525544,18.190928 9.6162626,20.3104404 13.338596,19.6602991 C17.0609294,19.0101577 19.7773154,15.7785188 19.7776894,11.9998178 C19.7776894,11.3862113 20.2751133,10.888785 20.8887168,10.888785 C21.5023203,10.888785 21.9997442,11.3862113 21.9997442,11.9998178 C21.9997099,16.8400427 18.5329414,20.9853151 13.7690477,21.8413977 C9.00515408,22.6974802 4.31207278,20.0185514 2.62687668,15.4811663 C0.941680576,10.9437811 2.7482686,5.85083589 6.91602308,3.38965046 C11.0837776,0.928465034 16.4159405,1.80576128 19.5754824,5.47250055 L20.1087755,4.94698208 C20.545968,4.51623039 21.2495741,4.52145337 21.6803238,4.95864794 C22.1110734,5.39584251 22.1058505,6.09945198 21.668658,6.5302037 L12.3204735,15.742887 C11.8856052,16.1721277 11.185738,16.1696459 10.7539248,15.7373318 L6.69200863,11.6742852 C6.27096012,11.2383394 6.2769817,10.5453817 6.70554253,10.1168189 C7.13410335,9.688256 7.82705768,9.68223439 8.26300138,10.1032849 L11.5460874,13.3863866 L17.9889353,7.03683462 L17.9889353,7.03683462 Z" id="path-1"></path>
+        <filter x="-15.0%" y="-15.0%" width="130.0%" height="130.0%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.125792177 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(老师)" transform="translate(-740.000000, -208.000000)" fill-rule="nonzero">
+            <g id="编组-8" transform="translate(732.000000, 66.000000)">
+                <g id="编组-7" transform="translate(0.000000, 75.000000)">
+                    <g id="路径" transform="translate(8.000000, 67.000000)">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                        <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 20 - 0
src/views/courseware-play/image/icon-song-active.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(三层)" transform="translate(-364.000000, -298.000000)" fill-rule="nonzero">
+            <g id="编组" transform="translate(306.000000, 0.000000)">
+                <g id="编组-8" transform="translate(0.000000, 93.500000)">
+                    <g id="编组-12" transform="translate(0.000000, 65.000000)">
+                        <g id="知识点目录/图片备份-3" transform="translate(58.000000, 140.000000)">
+                            <g id="编组" transform="translate(1.000000, 0.000000)">
+                                <path d="M9,0 L13,4 L13,13 C13,14.1 12.1,15 11,15 L2,15 C0.900000001,15 0,14.1 0,13 L0,2 C0,0.9 0.900000001,0 2,0 L9,0 Z M9.12286624,4.39507366 L9.05045121,4.40041053 L4.62790119,5.15709075 C4.43655776,5.19167453 4.28513683,5.3491615 4.28561715,5.52434895 L4.28561715,5.52434895 L4.23182216,9.53593936 C3.89020555,9.39268515 3.46572382,9.3940618 3.05944296,9.57324696 C2.32170786,9.89861591 1.93033971,10.6831644 2.18299927,11.3317032 L2.18299927,11.3317032 L2.20168239,11.3766901 C2.48234091,12.0123255 3.29959494,12.2398786 4.02247275,11.8921219 C4.56059052,11.6329597 4.90067764,11.1236487 4.92749856,10.6174852 L4.92749856,10.6174852 L4.97723571,6.93534117 C4.98142189,6.75800316 5.13114435,6.59642647 5.32418625,6.56593241 L5.32418625,6.56593241 L8.30000288,6.10080156 C8.30466938,6.098651 8.31400237,6.09434988 8.32036733,6.09628904 C8.51680615,6.07397444 8.6721386,6.20703285 8.66965088,6.38846059 L8.66965088,6.38846059 L8.5995415,9.09592309 C8.2484902,8.94608506 7.81030549,8.94997414 7.39554263,9.14388305 C6.66756943,9.47937063 6.29062966,10.2718872 6.5572887,10.9139742 C6.82394775,11.5560613 7.63229769,11.8023353 8.36323892,11.4606074 C8.94898573,11.1906651 9.30736799,10.629963 9.28887187,10.0889393 L9.28887187,10.0889393 L9.42646417,4.69000872 C9.42773374,4.6796787 9.42603527,4.67558898 9.42730484,4.66525896 C9.4140946,4.48619318 9.2481596,4.36776591 9.05045121,4.40041053 Z" id="形状结合" fill="#FF8057"></path>
+                                <path d="M9.00000001,0 L13,4 L10,4 C9.4,4 9.00000001,3.6 9.00000001,3 L9.00000001,0 Z" id="路径" fill="#CC4419" opacity="0.506022135" style="mix-blend-mode: multiply;"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 20 - 0
src/views/courseware-play/image/icon-song.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(三层)" transform="translate(-364.000000, -268.000000)" fill-rule="nonzero">
+            <g id="编组" transform="translate(306.000000, 0.000000)">
+                <g id="编组-8" transform="translate(0.000000, 93.500000)">
+                    <g id="编组-12" transform="translate(0.000000, 65.000000)">
+                        <g id="知识点目录/图片备份-6" transform="translate(58.000000, 110.000000)">
+                            <g id="编组" transform="translate(1.000000, 0.000000)">
+                                <path d="M9,0 L13,4 L13,13 C13,14.1 12.1,15 11,15 L2,15 C0.900000001,15 0,14.1 0,13 L0,2 C0,0.9 0.900000001,0 2,0 L9,0 Z M9.12286624,4.39507366 L9.05045121,4.40041053 L4.62790119,5.15709075 C4.43655776,5.19167453 4.28513683,5.3491615 4.28561715,5.52434895 L4.28561715,5.52434895 L4.23182216,9.53593936 C3.89020555,9.39268515 3.46572382,9.3940618 3.05944296,9.57324696 C2.32170786,9.89861591 1.93033971,10.6831644 2.18299927,11.3317032 L2.18299927,11.3317032 L2.20168239,11.3766901 C2.48234091,12.0123255 3.29959494,12.2398786 4.02247275,11.8921219 C4.56059052,11.6329597 4.90067764,11.1236487 4.92749856,10.6174852 L4.92749856,10.6174852 L4.97723571,6.93534117 C4.98142189,6.75800316 5.13114435,6.59642647 5.32418625,6.56593241 L5.32418625,6.56593241 L8.30000288,6.10080156 C8.30466938,6.098651 8.31400237,6.09434988 8.32036733,6.09628904 C8.51680615,6.07397444 8.6721386,6.20703285 8.66965088,6.38846059 L8.66965088,6.38846059 L8.5995415,9.09592309 C8.2484902,8.94608506 7.81030549,8.94997414 7.39554263,9.14388305 C6.66756943,9.47937063 6.29062966,10.2718872 6.5572887,10.9139742 C6.82394775,11.5560613 7.63229769,11.8023353 8.36323892,11.4606074 C8.94898573,11.1906651 9.30736799,10.629963 9.28887187,10.0889393 L9.28887187,10.0889393 L9.42646417,4.69000872 C9.42773374,4.6796787 9.42603527,4.67558898 9.42730484,4.66525896 C9.4140946,4.48619318 9.2481596,4.36776591 9.05045121,4.40041053 Z" id="形状结合" fill="#FFFFFF"></path>
+                                <path d="M9.00000001,0 L13,4 L10,4 C9.4,4 9.00000001,3.6 9.00000001,3 L9.00000001,0 Z" id="路径" fill="#B3B3B3" opacity="0.619"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 53 - 0
src/views/courseware-play/image/icon-start.svg

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="121px" height="38px" viewBox="0 0 121 38" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>按钮/小按钮/橙</title>
+    <defs>
+        <linearGradient x1="15.3622126%" y1="46.5566937%" x2="89.7647319%" y2="53.6358332%" id="linearGradient-1">
+            <stop stop-color="#FF9C63" offset="0%"></stop>
+            <stop stop-color="#FF7144" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="-67.2865798%" y1="54.9313571%" x2="100%" y2="50%" id="linearGradient-2">
+            <stop stop-color="#5BECFF" offset="0%"></stop>
+            <stop stop-color="#259CFE" offset="100%"></stop>
+        </linearGradient>
+        <rect id="path-3" x="0" y="0" width="121" height="38" rx="19"></rect>
+        <text id="text-4" font-family="PingFangSC-Medium, PingFang SC" font-size="16" font-weight="400" fill="#FFFFFF">
+            <tspan x="49" y="24">去练习</tspan>
+        </text>
+        <filter x="-4.2%" y="-4.5%" width="108.3%" height="118.2%" filterUnits="objectBoundingBox" id="filter-5">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0.108514357   0 0 0 0 0.562412481   0 0 0 0 0.907525104  0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-6">
+            <stop stop-color="#FCFFFF" offset="0%"></stop>
+            <stop stop-color="#D4F1FF" offset="100%"></stop>
+        </linearGradient>
+        <path d="M10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 Z M15.8740909,4.13727273 C15.7362351,3.65455394 15.2331964,3.37495234 14.7504546,3.51272728 L10.205,4.81181817 C9.89624241,4.89978885 9.65677854,5.14409039 9.57499999,5.45454546 L9.54545455,5.45454546 L9.54590909,11.0254545 C8.4211929,10.2420678 6.92120134,10.2669785 5.82311793,11.0872797 C4.72503453,11.907581 4.27567032,13.3388975 4.70785225,14.6396281 C5.14003419,15.9403587 6.35662277,16.8181818 7.72727272,16.8181818 L7.83636363,16.8163636 C9.5501842,16.7575695 10.9091192,15.3511924 10.9090909,13.6363636 L10.9090909,8.24636364 L15.25,7.00636364 C15.6400039,6.89481163 15.9089107,6.53837105 15.9090909,6.13272727 L15.9090909,4.38727272 C15.9090909,4.30271722 15.8973272,4.21857285 15.8740909,4.13727273 Z" id="path-7"></path>
+        <filter x="-10.0%" y="-5.0%" width="120.0%" height="120.0%" filterUnits="objectBoundingBox" id="filter-8">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0.108514357   0 0 0 0 0.562412481   0 0 0 0 0.907525104  0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件列表" transform="translate(-350.000000, -321.000000)">
+            <g id="编组-2" transform="translate(350.000000, 321.000000)">
+                <g id="button-normal">
+                    <use fill="url(#linearGradient-1)" xlink:href="#path-3"></use>
+                    <use fill="url(#linearGradient-2)" xlink:href="#path-3"></use>
+                </g>
+                <g id="确定" fill-rule="nonzero" fill="#FFFFFF" fill-opacity="1">
+                    <use filter="url(#filter-5)" xlink:href="#text-4"></use>
+                    <use xlink:href="#text-4"></use>
+                </g>
+                <g id="编组" transform="translate(24.000000, 9.000000)" fill-rule="nonzero">
+                    <g id="形状结合">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-8)" xlink:href="#path-7"></use>
+                        <use fill="url(#linearGradient-6)" xlink:href="#path-7"></use>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

File diff suppressed because it is too large
+ 4 - 0
src/views/courseware-play/image/icon-touping.svg


+ 22 - 0
src/views/courseware-play/image/icon-up.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M12.619886,7.97254617 L12.7071068,8.05025253 L16.2426407,11.5857864 C16.633165,11.9763107 16.633165,12.6094757 16.2426407,13 C15.880011,13.3626297 15.3081656,13.3885318 14.9156479,13.0777064 L14.8284271,13 L12.9994661,11.1713593 L13,19 C13,19.5522847 12.5522847,20 12,20 C11.4477153,20 11,19.5522847 11,19 L10.9994661,11.1703593 L9.17157288,13 C8.80894318,13.3626297 8.23709778,13.3885318 7.84458013,13.0777064 L7.75735931,13 C7.39472961,12.6373703 7.36882749,12.0655249 7.67965295,11.6730073 L7.75735931,11.5857864 L11.2928932,8.05025253 C11.6555229,7.68762283 12.2273683,7.66172071 12.619886,7.97254617 Z M18.363961,5.63603897 C21.8786797,9.15075759 21.8786797,14.8492424 18.363961,18.363961 C17.9734367,18.7544853 17.3402718,18.7544853 16.9497475,18.363961 C16.5592232,17.9734367 16.5592232,17.3402718 16.9497475,16.9497475 C19.6834175,14.2160774 19.6834175,9.78392257 16.9497475,7.05025253 C14.2160774,4.31658249 9.78392257,4.31658249 7.05025253,7.05025253 C4.31658249,9.78392257 4.31658249,14.2160774 7.05025253,16.9497475 C7.44077682,17.3402718 7.44077682,17.9734367 7.05025253,18.363961 C6.65972824,18.7544853 6.02656326,18.7544853 5.63603897,18.363961 C2.12132034,14.8492424 2.12132034,9.15075759 5.63603897,5.63603897 C9.15075759,2.12132034 14.8492424,2.12132034 18.363961,5.63603897 Z" id="path-1"></path>
+        <filter x="-33.3%" y="-35.3%" width="166.7%" height="170.6%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放/全屏(伴学端)" transform="translate(-90.000000, -82.000000)" fill-rule="nonzero">
+            <g id="编组-8备份" transform="translate(82.000000, 68.000000)">
+                <g id="形状结合" transform="translate(8.000000, 14.000000)">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

File diff suppressed because it is too large
+ 9 - 0
src/views/courseware-play/image/icon-video-active.svg


File diff suppressed because it is too large
+ 9 - 0
src/views/courseware-play/image/icon-video.svg


BIN
src/views/courseware-play/image/icon-videobg.png


+ 1 - 0
src/views/courseware-play/image/icon-zhibo.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1673454396647" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1930" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M74.666667 853.333333c-17.066667 0-32-14.933333-32-32v-426.666666c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v426.666666c0 17.066667-14.933333 32-32 32zM366.933333 853.333333c-17.066667 0-32-14.933333-32-32v-618.666666c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v618.666666c0 17.066667-14.933333 32-32 32zM657.066667 853.333333c-17.066667 0-32-14.933333-32-32v-341.333333c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v341.333333c0 17.066667-12.8 32-32 32zM949.333333 853.333333c-17.066667 0-32-14.933333-32-32v-512c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v512c0 17.066667-14.933333 32-32 32z" fill="#FF8057" p-id="1931"></path></svg>

+ 250 - 0
src/views/courseware-play/index.module.less

@@ -0,0 +1,250 @@
+.playContent {
+  width: 100vw;
+  height: 100vh;
+  background-color: #000;
+  overflow: hidden;
+  --plyr-color-main: var(--van-primary);
+  --plyr-range-track-height: 3px;
+}
+
+.coursewarePlay {
+  position: relative;
+  height: 100vh;
+  margin: 0 auto;
+  overflow: hidden;
+}
+
+.playModel {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  box-shadow: inset 0px 0px 164px 0px rgba(0, 0, 0, 1);
+  pointer-events: none;
+}
+
+.headerContainer {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 40px;
+  background: linear-gradient(180deg, rgba(0, 0, 0, 0.6), transparent);
+  transition: transform 0.5s;
+  box-sizing: border-box;
+
+  div {
+    box-sizing: border-box;
+  }
+}
+
+.backBtn {
+  color: #fff;
+  height: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 10;
+  padding: 0 15px;
+
+  :global {
+    .van-icon {
+      margin-right: 8px;
+    }
+  }
+}
+
+.headRight {
+  position: relative;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+  margin-left: auto;
+  height: 100%;
+  padding-right: 15px;
+
+  .rightBtn {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100%;
+    padding: 0 10px;
+
+    img {
+      width: 22px;
+      height: 22px;
+      display: block;
+    }
+  }
+}
+
+.menu {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 12px;
+  color: #fff;
+}
+
+.tabsContent {
+  width: 100vw;
+  height: 100vh;
+
+  :global {
+    .van-tabs__wrap {
+      display: none !important;
+    }
+
+    .van-tabs__content {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+.wraps {
+  width: 100%;
+  height: 100%;
+  transform-style: preserve-3d;
+  perspective: (32rem);
+  transition-timing-function: initial;
+}
+
+.itemDiv {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+  transform-style: preserve-3d;
+  transition-property: transform, opacity, height;
+  backface-visibility: hidden;
+  overflow: hidden;
+  z-index: 1;
+
+  &.itemActive {
+    z-index: 10;
+  }
+
+  &.acitveAnimation {
+    transition-duration: .8s;
+  }
+
+  &.show {
+    display: block;
+  }
+
+  &.hide {
+    display: none;
+  }
+
+  video {
+    width: 100%;
+    height: 100%;
+  }
+
+  img {
+    display: block;
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+}
+
+.rightFixedBtns {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  right: 12px;
+  z-index: 10;
+}
+
+.fullBtn {
+  width: 38px;
+  height: 46px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #fff;
+  justify-content: space-evenly;
+  overflow: hidden;
+  white-space: nowrap;
+  background: rgba(51, 51, 51, 0.8);
+  border-radius: 6px;
+  overflow: hidden;
+  &:not(:last-child):not(:first-child) {
+    margin: 8px 0;
+  }
+
+  &:active {
+    opacity: .8;
+  }
+  &.btnsDisabled {
+    opacity: .3;
+    pointer-events: none;
+  }
+}
+
+
+.popup {
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.overlayClass {
+  --van-overlay-background: transparent;
+}
+
+:global {
+
+  .top-enter-active,
+  .top-leave-active {
+    transition: transform 0.5s;
+  }
+
+  .top-enter-from,
+  .top-leave-to {
+    transform: translateY(-100%);
+  }
+
+  .right-enter-active,
+  .right-leave-active {
+    transition: all 0.5s;
+  }
+
+  .right-enter-from,
+  .right-leave-to {
+    right: -60px;
+    opacity: 0;
+  }
+
+  .bottom-enter-active,
+  .bottom-leave-active {
+    transition: transform 0.5s;
+  }
+
+  .bottom-enter-from,
+  .bottom-leave-to {
+    transform: translateY(100%);
+  }
+}
+
+.loadWrap {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(45deg, #21232a, #111218);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}

+ 634 - 6
src/views/courseware-play/index.tsx

@@ -1,8 +1,636 @@
-import { defineComponent } from "vue";
+import { closeToast, Icon, Popup, showDialog, showToast } from 'vant';
+import {
+  defineComponent,
+  onMounted,
+  reactive,
+  nextTick,
+  onUnmounted,
+  ref,
+  watch,
+  Transition
+} from 'vue';
+import iconBack from './image/back.svg';
+import styles from './index.module.less';
+import 'plyr/dist/plyr.css';
+import { useRoute, useRouter } from 'vue-router';
+import {
+  listenerMessage,
+  postMessage,
+  promisefiyPostMessage
+} from '@/helpers/native-message';
+import MusicScore from './component/musicScore';
+import iconMenu from './image/icon-menu.svg';
+import iconDian from './image/icon-dian.svg';
+import iconPoint from './image/icon-point.svg';
+import iconUp from './image/icon-up.svg';
+import iconDown from './image/icon-down.svg';
+import Points from './component/points';
+import { browser, getSecondRPM } from '@/helpers/utils';
+import { Vue3Lottie } from 'vue3-lottie';
+import playLoadData from './datas/data.json';
+import { usePageVisibility, useRect } from '@vant/use';
+import VideoPlay from './component/video-play';
+import Tool, { ToolItem, ToolType } from './component/tool';
 
 export default defineComponent({
-    name: 'CoursewarePlay',
-    setup() {
-        return () => <div></div>
-    }
-})
+  name: 'CoursewarePlay',
+  setup() {
+    const pageVisibility = usePageVisibility();
+    const isPlay = ref(false);
+    /** 页面显示和隐藏 */
+    watch(pageVisibility, value => {
+      const activeItem = data.itemList[popupData.activeIndex];
+      if (activeItem.type != 'VIDEO') return;
+      if (value == 'hidden') {
+        isPlay.value = !activeItem.videoEle?.paused;
+        togglePlay(activeItem, false);
+      } else {
+        // 页面显示,并且
+        if (isPlay.value) togglePlay(activeItem, true);
+      }
+    });
+    /** 设置播放容器 16:9 */
+    const parentContainer = reactive({
+      width: '100vw'
+    });
+    const setContainer = () => {
+      let min = Math.min(screen.width, screen.height);
+      let max = Math.max(screen.width, screen.height);
+      let width = min * (16 / 9);
+      if (width > max) {
+        parentContainer.width = '100vw';
+        return;
+      } else {
+        parentContainer.width = width + 'px';
+      }
+    };
+    const handleInit = (type = 0) => {
+      //设置容器16:9
+      setContainer();
+      // 横屏
+      postMessage(
+        {
+          api: 'setRequestedOrientation',
+          content: {
+            orientation: type
+          }
+        },
+        () => {
+          console.log(234);
+        }
+      );
+      // 头,包括返回箭头
+      // postMessage({
+      //   api: 'setTitleBarVisibility',
+      //   content: {
+      //     status: type
+      //   }
+      // })
+      // 安卓的状态栏
+      postMessage({
+        api: 'setStatusBarVisibility',
+        content: {
+          isVisibility: type
+        }
+      });
+      // 进入页面设置常量
+      postMessage({
+        api: 'keepScreenLongLight',
+        content: {
+          isOpenLight: type ? true : false
+        }
+      });
+    };
+    handleInit();
+    onUnmounted(() => {
+      handleInit(1);
+      window.removeEventListener('message', iframeHandle);
+    });
+
+    const route = useRoute();
+    const router = useRouter();
+    const headeRef = ref();
+    const data = reactive({
+      detail: null,
+      knowledgePointList: [] as any,
+      itemList: [] as any,
+      showHead: true,
+      isCourse: false,
+      isRecordPlay: false,
+      videoRefs: {} as any[]
+    });
+    const activeData = reactive({
+      isAutoPlay: true, // 是否自动播放
+      nowTime: 0,
+      model: true, // 遮罩
+      isAnimation: true, // 是否动画
+      videoBtns: true, // 视频
+      currentTime: 0,
+      duration: 0,
+      timer: null as any,
+      item: null as any
+    });
+    const getTempList = async (materialList: any, name: any) => {
+      const list: any = [];
+      const browserInfo = browser();
+      for (let j = 0; j < materialList.length; j++) {
+        const material = materialList[j];
+
+        list.push({
+          ...material,
+          iframeRef: null,
+          videoEle: null,
+          tabName: name,
+          autoPlay: false, //加载完成是否自动播放
+          isprepare: false, // 视频是否加载完成
+          isRender: false // 是否渲染了
+        });
+      }
+      return list;
+    };
+    const getDetail = async () => {
+      data.knowledgePointList = [
+        {
+          id: '1',
+          name: '歌曲表演 大鹿',
+          type: 'VIDEO',
+          content: 'https://courseware.lexiaoya.cn/%E5%BF%85%E5%AD%A6%E5%BF%85%E7%9C%8B-%E8%90%A8%E5%85%8B%E6%96%AF-1-C4-4.mp4'
+        },
+        {
+          id: '2',
+          name: '知识 音的高低',
+          type: 'IMG',
+          content: 'https://gyt.ks3-cn-beijing.ksyuncs.com/courseware/1686815979899.png'
+        },
+        {
+          id: '3',
+          name: '欣赏 永远在童话里',
+          type: 'IMG',
+          content: 'https://gyt.ks3-cn-beijing.ksyuncs.com/courseware/1686815979899.png'
+        },
+        {
+          id: '4',
+          name: '唱歌 小红帽',
+          type: 'SONG',
+          content: '11707'
+        }
+      ];
+      data.itemList = data.knowledgePointList.map((m: any) => {
+        return {
+          ...m,
+          iframeRef: null,
+          videoEle: null,
+          autoPlay: false, //加载完成是否自动播放
+          isprepare: false, // 视频是否加载完成
+          isRender: false // 是否渲染了
+        };
+      });
+    };
+
+    // ifram事件处理
+    const iframeHandle = (ev: MessageEvent) => {
+      if (ev.data?.api === 'headerTogge') {
+        activeData.model =
+          ev.data.show || (ev.data.playState == 'play' ? false : true);
+      }
+    };
+
+    onMounted(() => {
+      postMessage({
+        api: 'courseLoading',
+        content: {
+          show: false,
+          type: 'fullscreen'
+        }
+      });
+      getDetail();
+      window.addEventListener('message', iframeHandle);
+    });
+
+    const playRef = ref();
+    // 返回
+    const goback = () => {
+      try {
+        playRef.value?.handleOut();
+      } catch (error) {}
+      postMessage({ api: 'goBack' });
+    };
+
+    const popupData = reactive({
+      open: false,
+      activeIndex: 0,
+      tabActive: '',
+      tabName: '',
+      itemActive: '',
+      itemName: '',
+      guideOpen: false,
+      toolOpen: false // 工具弹窗控制
+    });
+
+    /**停止所有的播放 */
+    const handleStop = () => {
+      for (let i = 0; i < data.itemList.length; i++) {
+        const activeItem = data.itemList[i];
+        if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
+          activeItem.videoEle.stop();
+        }
+        // console.log('🚀 ~ activeItem:', activeItem)
+        // 停止曲谱的播放
+        if (activeItem.type === 'SONG') {
+          activeItem.iframeRef?.contentWindow?.postMessage(
+            { api: 'setPlayState' },
+            '*'
+          );
+        }
+      }
+    };
+    // 切换素材
+    const toggleMaterial = (itemActive: any) => {
+      const index = data.itemList.findIndex((n: any) => n.id == itemActive);
+      if (index > -1) {
+        handleSwipeChange(index);
+      }
+    };
+    /** 延迟收起模态框 */
+    const setModelOpen = () => {
+      clearTimeout(activeData.timer);
+      closeToast();
+      activeData.timer = setTimeout(() => {
+        activeData.model = false;
+        Object.values(data.videoRefs).map((n: any) =>
+          n.toggleHideControl(false)
+        );
+      }, 4000);
+    };
+    /** 立即收起所有的模态框 */
+    const clearModel = () => {
+      clearTimeout(activeData.timer);
+      closeToast();
+      activeData.model = false;
+      Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(false));
+    };
+    const toggleModel = (type: boolean = true) => {
+      activeData.model = type;
+      Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(type));
+    };
+
+    // 双击
+    const handleDbClick = (item: any) => {
+      if (item && item.type === 'VIDEO') {
+        const videoEle: HTMLVideoElement = item.videoEle;
+        if (videoEle) {
+          if (videoEle.paused) {
+            closeToast();
+            videoEle.play();
+          } else {
+            showToast('已暂停');
+            videoEle.pause();
+          }
+        }
+      }
+    };
+
+    // 切换播放
+    const togglePlay = (m: any, isPlay: boolean) => {
+      if (isPlay) {
+        m.videoEle?.play();
+      } else {
+        m.videoEle?.pause();
+      }
+    };
+
+    const showIndex = ref(-4);
+    const effectIndex = ref(3);
+    const effects = [
+      {
+        prev: {
+          transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
+        },
+        next: {
+          transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
+        }
+      },
+      {
+        prev: {
+          transform: 'translate3d(-100%, 0, -800px)'
+        },
+        next: {
+          transform: 'translate3d(100%, 0, -800px)'
+        }
+      },
+      {
+        prev: {
+          transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
+        },
+        next: {
+          transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
+        }
+      },
+      {
+        prev: {
+          transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
+        },
+        next: {
+          transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
+        }
+      },
+      // 风车4
+      {
+        prev: {
+          transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
+          opacity: 0
+        },
+        next: {
+          transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
+          opacity: 0
+        }
+      },
+      // 翻页5
+      {
+        prev: {
+          transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
+          opacity: 0
+        },
+        next: {
+          transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
+          opacity: 0
+        },
+        current: { transitionDelay: '700ms' }
+      }
+    ];
+
+    const acitveTimer = ref();
+    // 轮播切换
+    const handleSwipeChange = (index: number) => {
+      // 如果是当前正在播放 或者是视频最后一个
+      if (popupData.activeIndex == index) return;
+      handleStop();
+      clearTimeout(acitveTimer.value);
+      checkedAnimation(popupData.activeIndex, index);
+      popupData.activeIndex = index;
+
+      acitveTimer.value = setTimeout(
+        () => {
+          const item = data.itemList[index];
+          if (item) {
+            popupData.tabActive = item.knowledgePointId;
+            popupData.itemActive = item.id;
+            popupData.itemName = item.name;
+            popupData.tabName = item.tabName;
+            if (item.type == 'SONG') {
+              activeData.model = true;
+            }
+            if (item.type === 'VIDEO') {
+              // 自动播放下一个视频
+              clearTimeout(activeData.timer);
+              closeToast();
+              item.autoPlay = true;
+              nextTick(() => {
+                item.videoEle?.play();
+              });
+            }
+          }
+        //   requestAnimationFrame(() => {
+        //     const _effectIndex = effectIndex.value + 1;
+        //     effectIndex.value =
+        //       _effectIndex >= effects.length - 1 ? 0 : _effectIndex;
+        //   });
+        },
+        activeData.isAnimation ? 800 : 0
+      );
+    };
+
+    /** 是否有转场动画 */
+    const checkedAnimation = (index: number, nextIndex?: number) => {
+      const item = data.itemList[index];
+      const nextItem = data.itemList[nextIndex!];
+      if (nextItem) {
+        if (nextItem.knowledgePointId != item.knowledgePointId) {
+          activeData.isAnimation = true;
+          return;
+        }
+        const videoEle = item.videoEle;
+        const nextVideo = nextItem.videoEle;
+        if (videoEle && videoEle.duration < 8 && index < nextIndex!) {
+          activeData.isAnimation = false;
+        } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex!) {
+          activeData.isAnimation = false;
+        } else {
+          activeData.isAnimation = true;
+        }
+      } else {
+        activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true;
+      }
+    };
+
+    // 上一个知识点, 下一个知识点
+    const handlePreAndNext = (type: string) => {
+      if (type === 'up') {
+        handleSwipeChange(popupData.activeIndex - 1);
+      } else {
+        handleSwipeChange(popupData.activeIndex + 1);
+      }
+    };
+
+    /** 弹窗关闭 */
+    const handleClosePopup = () => {
+      const item = data.itemList[popupData.activeIndex];
+      if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
+        setModelOpen();
+      }
+    };
+
+    return () => (
+      <div id="playContent" class={styles.playContent}>
+        <div
+          onClick={() => {
+            clearTimeout(activeData.timer);
+            activeData.model = !activeData.model;
+            Object.values(data.videoRefs).map((n: any) =>
+              n.toggleHideControl(activeData.model)
+            );
+          }}>
+          <div
+            class={styles.coursewarePlay}
+            style={{ width: parentContainer.width }}
+            onClick={(e: Event) => {
+              e.stopPropagation();
+              setModelOpen();
+            }}>
+            <div class={styles.wraps}>
+              {data.itemList.map((m: any, mIndex: number) => {
+                const isRender =
+                  m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2;
+                const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
+                if (isRender) {
+                  m.isRender = true;
+                }
+                return isRender ? (
+                  <div
+                    key={'index' + mIndex}
+                    class={[
+                      styles.itemDiv,
+                      popupData.activeIndex === mIndex && styles.itemActive,
+                      activeData.isAnimation && styles.acitveAnimation,
+                      Math.abs(popupData.activeIndex - mIndex) < 2
+                        ? styles.show
+                        : styles.hide
+                    ]}
+                    style={
+                      mIndex < popupData.activeIndex
+                        ? effects[effectIndex.value].prev
+                        : mIndex > popupData.activeIndex
+                        ? effects[effectIndex.value].next
+                        : {}
+                    }
+                    onClick={(e: Event) => {
+                      e.stopPropagation();
+                      clearTimeout(activeData.timer);
+                      if (Date.now() - activeData.nowTime < 300) {
+                        handleDbClick(m);
+                        return;
+                      }
+                      activeData.nowTime = Date.now();
+                      activeData.timer = setTimeout(() => {
+                        activeData.model = !activeData.model;
+                        Object.values(data.videoRefs).map((n: any) =>
+                          n.toggleHideControl(activeData.model)
+                        );
+                        if (activeData.model) {
+                          setModelOpen();
+                        }
+                      }, 300);
+                    }}>
+                    {m.type === 'VIDEO' ? (
+                      <>
+                        <VideoPlay
+                          ref={(v: any) => (data.videoRefs[mIndex] = v)}
+                          item={m}
+                          isEmtry={isEmtry}
+                          onLoadedmetadata={(videoItem: any) => {
+                            m.videoEle = videoItem;
+                            m.isprepare = true;
+                          }}
+                          onTogglePlay={(paused: boolean) => {
+                            m.autoPlay = false;
+                            if (
+                              paused ||
+                              popupData.open ||
+                              popupData.guideOpen
+                            ) {
+                              clearTimeout(activeData.timer);
+                            } else {
+                              setModelOpen();
+                            }
+                          }}
+                          onEnded={() => {
+                            const _index = popupData.activeIndex + 1;
+                            if (_index < data.itemList.length) {
+                              handleSwipeChange(_index);
+                            }
+                          }}
+                          onReset={() => {
+                            if (!m.videoEle?.paused) {
+                              setModelOpen();
+                            }
+                          }}
+                        />
+                        <Transition name="van-fade">
+                          {!m.isprepare && (
+                            <div class={styles.loadWrap}>
+                              <Vue3Lottie
+                                animationData={playLoadData}></Vue3Lottie>
+                            </div>
+                          )}
+                        </Transition>
+                      </>
+                    ) : m.type === 'IMG' ? (
+                      <img src={m.content} />
+                    ) : (
+                      <MusicScore
+                        activeModel={activeData.model}
+                        data-vid={m.id}
+                        music={m}
+                        onSetIframe={(el: any) => {
+                          m.iframeRef = el;
+                        }}
+                      />
+                    )}
+                  </div>
+                ) : null;
+              })}
+            </div>
+            <Transition name="right">
+              {activeData.model && (
+                <div
+                  class={styles.rightFixedBtns}
+                  onClick={(e: Event) => {
+                    e.stopPropagation();
+                    clearTimeout(activeData.timer);
+                  }}>
+                  <div
+                    class={[styles.fullBtn, styles.point]}
+                    onClick={() => (popupData.open = true)}>
+                    <img src={iconMenu} />
+                    <span>课件</span>
+                  </div>
+
+                  <div
+                    class={[
+                      styles.fullBtn,
+                      popupData.activeIndex == 0 && styles.btnsDisabled
+                    ]}
+                    onClick={() => handlePreAndNext('up')}>
+                    <img src={iconUp} />
+                    <span style={{ textAlign: 'center' }}>上一个</span>
+                  </div>
+
+                  <div
+                    class={[
+                      styles.fullBtn,
+                      popupData.activeIndex == data.itemList.length - 1 &&
+                        styles.btnsDisabled
+                    ]}
+                    onClick={() => handlePreAndNext('down')}>
+                    <span style={{ textAlign: 'center' }}>下一个</span>
+                    <img src={iconDown} />
+                  </div>
+                </div>
+              )}
+            </Transition>
+          </div>
+        </div>
+
+        <div
+          style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
+          class={styles.headerContainer}
+          ref={headeRef}>
+          <div class={styles.backBtn} onClick={() => goback()}>
+            <Icon name={iconBack} />
+            返回
+          </div>
+          <div class={styles.menu}>{popupData.itemName}</div>
+        </div>
+
+        <Popup
+          class={styles.popup}
+          style={{ background: 'rgba(0,0,0, 0.75)' }}
+          overlayClass={styles.overlayClass}
+          position="right"
+          round
+          v-model:show={popupData.open}
+          onClose={handleClosePopup}>
+          <Points
+            data={data.knowledgePointList}
+            tabActive={popupData.tabActive}
+            itemActive={popupData.itemActive}
+            onHandleSelect={(res: any) => {
+              popupData.open = false;
+              toggleMaterial(res.itemActive);
+            }}
+          />
+        </Popup>
+      </div>
+    );
+  }
+});

+ 112 - 0
src/views/courseware-play/playRecordTime.tsx

@@ -0,0 +1,112 @@
+import request from '@/helpers/request'
+import { getSecondRPM } from '@/helpers/utils'
+import { state } from '@/state'
+import { usePageVisibility } from '@vant/use'
+import dayjs from 'dayjs'
+import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
+import { useRoute } from 'vue-router'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'playRecordTime',
+  props: {
+    list: {
+      type: Array,
+      default: () => []
+    }
+  },
+  setup(props, { expose }) {
+    const pageVisibility = usePageVisibility()
+
+    watch(pageVisibility, (value) => {
+      if (value == 'hidden') {
+        handleOut()
+      } else {
+        // 页面显示
+        handleStartInterval()
+      }
+    })
+    const handleOut = () => {
+      clearInterval(timerRecord.value)
+      handleRecord(true)
+    }
+    expose({
+      handleOut
+    })
+    const route = useRoute()
+    const saveModel = reactive({
+      loading: true,
+      /**当前时长 */
+      currentTime: 0,
+      /**记录的开始时间 */
+      startTime: 0,
+      timer: null as any,
+      /** 已经播放的时间 */
+      playTime: 0
+    })
+    /** 建议学习总时长 */
+    const total = computed(() => {
+      const _total = props.list.reduce(
+        (_total: number, item: any) => _total + (item.totalMaterialTimeSecond || 0),
+        0
+      )
+      return _total
+    })
+
+    const getPlayTime = async () => {
+      saveModel.loading = true
+      try {
+        const res: any = await request.post(
+          `${state.platformApi}/courseSchedule/getCoursewarePlayTime?courseScheduleId=${route.query.courseId}`
+        )
+        if (res.data) {
+          saveModel.playTime = res.data
+        }
+      } catch (error) {}
+      saveModel.loading = false
+      handleStartInterval()
+    }
+
+    /** 记录时长 */
+    const handleRecord = (isOut = false) => {
+      saveModel.currentTime++
+      const playTime = saveModel.currentTime - saveModel.startTime
+      // 1分钟记录1次
+      if (playTime >= 5 || isOut) {
+        console.log('isOut', isOut)
+        saveModel.startTime = saveModel.currentTime
+        request.post(`${state.platformApi}/courseSchedule/coursewarePlayTime`, {
+          params: {
+            courseScheduleId: route.query.courseId,
+            playTime
+          },
+          hideLoading: true
+        })
+      }
+    }
+    const timerRecord = ref()
+    const handleStartInterval = () => {
+      clearInterval(timerRecord.value)
+      timerRecord.value = setInterval(() => handleRecord(), 1000)
+    }
+    onMounted(() => {
+      getPlayTime()
+    })
+    onUnmounted(() => {
+      clearInterval(timerRecord.value)
+    })
+    return () => (
+      <div
+        class={styles.playRecordTimeWrap}
+        style={{ display: saveModel.loading || (saveModel.currentTime + saveModel.playTime > total.value) ? 'none' : '' }}
+      >
+        <div class={styles.playRecordTime}>
+          <div class={styles.timeLoad}></div>
+          <div>
+            {getSecondRPM(saveModel.currentTime + saveModel.playTime)} / {getSecondRPM(total.value)}
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

Some files were not shown because too many files changed in this diff