Browse Source

Merge branch 'hqyDev' of http://git.dayaedu.com/huangqiyong/pptList into online

黄琪勇 2 tháng trước cách đây
mục cha
commit
0f82c2f794
29 tập tin đã thay đổi với 1189 bổ sung966 xóa
  1. 1 1
      .env.development
  2. 41 2
      src/api/pptOperate.ts
  3. 2 2
      src/components/Empty/Empty.vue
  4. 4 2
      src/components/PopoverMenuItem.vue
  5. 6 3
      src/components/ellipsisScroll/ellipsisScroll.vue
  6. 3 2
      src/hooks/useCreateElement.ts
  7. 27 11
      src/messageHooks/pptScreen.ts
  8. 8 1
      src/queryParams/index.ts
  9. 9 4
      src/store/pptWork.ts
  10. 3 0
      src/types/slides.ts
  11. 3 23
      src/views/Editor/CanvasTool/index.vue
  12. 1 1
      src/views/Editor/EditorHeader/index.vue
  13. 11 1
      src/views/Editor/Thumbnails/index.vue
  14. 4 9
      src/views/Screen/BaseView.vue
  15. 2 17
      src/views/Screen/PresenterView.vue
  16. 1 1
      src/views/Screen/ScreenElement.vue
  17. 54 52
      src/views/Screen/hooks/useExecPlay.ts
  18. 2 14
      src/views/Screen/index.vue
  19. 66 55
      src/views/components/element/AudioElement/AudioPlayer.vue
  20. 2 0
      src/views/components/element/AudioElement/ScreenAudioElement.vue
  21. 16 16
      src/views/components/element/VideoElement/ScreenVideoElement.vue
  22. 95 72
      src/views/components/element/VideoElement/VideoPlayer/index.vue
  23. 15 2
      src/views/components/element/cloudCoachElement/BaseCloudCoachElement.vue
  24. 780 50
      src/views/components/element/cloudCoachElement/cloudCoachList/cloudCoachList.vue
  25. 0 606
      src/views/components/element/cloudCoachElement/cloudCoachList/cloudCoachList123.vue
  26. BIN
      src/views/components/element/cloudCoachElement/cloudCoachList/imgs/jiao.png
  27. BIN
      src/views/components/element/cloudCoachElement/cloudCoachList/imgs/musicBg.png
  28. 32 18
      src/views/components/element/cloudCoachElement/cloudCoachPlayer/cloudCoachPlayer.vue
  29. 1 1
      src/viewsframe/errorPage/errorPage.vue

+ 1 - 1
.env.development

@@ -1,5 +1,5 @@
 
-VITE_APP_URL = "http://192.168.3.122:9527/pptApi"
+VITE_APP_URL = "http://localhost:9527/pptApi"
 
 ## 云教练地址
 VITE_YJL_URL = "https://test.kt.colexiu.com/instrument"

+ 41 - 2
src/api/pptOperate.ts

@@ -13,7 +13,7 @@ export const getTeacherChapterKnowledgeMaterial = (id: string, fromType: queryPa
   })
 }
 
-// 保存信息
+// 老师端 保存信息
 export const putTeacherChapterKnowledgeMaterial = (data: { id: string; dataJson: string }) => {
   return httpAxios.axioseRquest({
     method: "post",
@@ -22,11 +22,50 @@ export const putTeacherChapterKnowledgeMaterial = (data: { id: string; dataJson:
   })
 }
 
+// 平台端 保存信息
+export const putChapterKnowledgeMaterialUpdate = (data: { id: string; dataJson: string }) => {
+  return httpAxios.axioseRquest({
+    method: "post",
+    url: "/edu-app/chapterKnowledgeMaterial/update",
+    data
+  })
+}
+
 // 获取曲目信息
-export const getMaterialQueryPage = (data: Record<string, any>) => {
+export const getMaterialQueryPage = (data: Record<string, any>, abortController: AbortController) => {
   return httpAxios.axioseRquest({
+    signal: abortController.signal,
     method: "post",
     url: "/edu-app/material/queryPage",
     data
   })
 }
+
+// 获取乐器信息
+export const getSubjectListApi = () => {
+  return httpAxios.axioseRquest({
+    method: "post",
+    url: "/edu-app/subject/list",
+    data: {
+      delFlag: 0,
+      page: 1,
+      rows: 999
+    }
+  })
+}
+// 获取课件教材
+export const getMusicTagTreeApi = () => {
+  return httpAxios.axioseRquest({
+    method: "get",
+    url: "/edu-app/musicTag/tree"
+  })
+}
+
+// 收藏课件 取消收藏
+export const favoriteApi = (data: { favoriteFlag: 0 | 1; materialId: string; type: string }) => {
+  return httpAxios.axioseRquest({
+    method: "post",
+    url: "/edu-app/material/favorite",
+    data
+  })
+}

+ 2 - 2
src/components/Empty/Empty.vue

@@ -8,7 +8,7 @@
 <script setup lang="ts">
 const props = withDefaults(
   defineProps<{
-    text: string
+    text?: string
   }>(),
   {
     text: "暂无内容"
@@ -27,7 +27,7 @@ const props = withDefaults(
   .text {
     margin-top: 6px;
     font-size: 16px;
-    color: #777777;
+    color: #999999;
   }
 }
 </style>

+ 4 - 2
src/components/PopoverMenuItem.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="popover-menu-item" :class="{ center: center }" @click="emit('click')">
+  <div class="popover-menu-item" :class="[center && 'center', active && 'active']" @click="emit('click')">
     <slot></slot>
   </div>
 </template>
@@ -8,6 +8,7 @@
 withDefaults(
   defineProps<{
     center?: boolean
+    active?: boolean
   }>(),
   {
     center: false
@@ -29,7 +30,8 @@ const emit = defineEmits<{
   &.center {
     text-align: center;
   }
-  &:hover {
+  &:hover,
+  &.active {
     background: #f5f6fa;
   }
   & + .popover-menu-item {

+ 6 - 3
src/components/ellipsisScroll/ellipsisScroll.vue

@@ -1,7 +1,10 @@
 <template>
-  <div ref="ellipsisScrollDom" :class="[isScroll && 'isScroll', isScroll && props.autoScroll && 'autoScroll']" class="ellipsisScroll">
-    {{ props.title }}
-  </div>
+  <div
+    ref="ellipsisScrollDom"
+    :class="[isScroll && 'isScroll', isScroll && props.autoScroll && 'autoScroll']"
+    v-html="props.title"
+    class="ellipsisScroll"
+  ></div>
 </template>
 
 <script setup lang="ts">

+ 3 - 2
src/hooks/useCreateElement.ts

@@ -322,7 +322,7 @@ export default () => {
    * 创建云教练元素
    * @param url 云教练地址
    */
-  const createCloudCoachElement = (sid: string) => {
+  const createCloudCoachElement = (sid: string, title: string) => {
     createElement({
       type: "elf",
       subtype: "elf-sing-play",
@@ -332,7 +332,8 @@ export default () => {
       rotate: 0,
       left: 0,
       top: 0,
-      sid
+      sid,
+      title
     })
   }
 

+ 27 - 11
src/messageHooks/pptScreen.ts

@@ -1,28 +1,44 @@
-import { onMounted, onUnmounted, watch } from "vue"
+import { onMounted, onUnmounted, watch, type Ref, type ComputedRef } from "vue"
 import { useSlidesStore } from "@/store"
 
-export const changePageSlideMes = (changePageSlide: (type: "prev" | "next") => void) => {
+export const changePageSlideMes = (
+  execPrev: () => void,
+  execNext: () => void,
+  animationIndex: Ref<number>,
+  formatedAnimations: ComputedRef<any[]>
+) => {
   const slidesStore = useSlidesStore()
 
   /** 初始化ppt完成之后给父级传递消息 */
   function pptInitMes() {
-    window.parent.postMessage({ type: "initPPT", content: { slidesLen: slidesStore.slides.length } }, "*")
+    window.parent.postMessage(
+      { type: "initPPT", content: { slidesLen: slidesStore.slides.length, isAnimationed: animationIndex.value === formatedAnimations.value.length } },
+      "*"
+    )
   }
   function handleMessage(event: any) {
     const { type, content } = event.data || {}
     if (type === "changePageSlide") {
-      changePageSlide(content)
+      /*  翻页 */
+      if (content === "prev") {
+        execPrev()
+      } else if (content === "next") {
+        execNext()
+      }
     }
   }
 
   pptInitMes()
-  watch(
-    () => slidesStore.slideIndex,
-    () => {
-      // 翻页完成之后的事件
-      window.parent.postMessage({ type: "changeSlideIndex", content: { slideIndex: slidesStore.slideIndex } }, "*")
-    }
-  )
+  watch([() => slidesStore.slideIndex, animationIndex], () => {
+    // 翻页完成之后的事件  isAnimationed 动画结束
+    window.parent.postMessage(
+      {
+        type: "changeSlideIndex",
+        content: { slideIndex: slidesStore.slideIndex, isAnimationed: animationIndex.value === formatedAnimations.value.length }
+      },
+      "*"
+    )
+  })
   onMounted(() => {
     window.addEventListener("message", handleMessage)
   })

+ 8 - 1
src/queryParams/index.ts

@@ -5,16 +5,23 @@ import router from "@/router"
 export type queryParamsType = {
   hideFullScreen: boolean
   fromType: "PLATFORM" | "TEACHER" | "CLASS"
+  instrumentId: string
+  lessonCoursewareKnowledgeId: string
 }
 const queryParams = reactive<queryParamsType>({
   hideFullScreen: false, // 隐藏预览时候的全屏按钮
-  fromType: "TEACHER"
+  fromType: "TEACHER", // 用于区分老师端 平台,或者学生端
+  lessonCoursewareKnowledgeId: "", // 课件id 老师端带过来查相关课件
+  instrumentId: "" // 传过来的乐器值,用于ppt里面云教练带什么乐器
 })
 
 export function initQueryParams() {
   const query = router.currentRoute.value.query
   queryParams.hideFullScreen = !!query.hideFullScreen
   query.fromType && (queryParams.fromType = query.fromType as any)
+  query.lessonCoursewareKnowledgeId && (queryParams.lessonCoursewareKnowledgeId = query.lessonCoursewareKnowledgeId as any)
+  query.instrumentId && (queryParams.instrumentId = query.instrumentId as any)
+  console.log(queryParams, "携带参数")
 }
 
 export default queryParams

+ 9 - 4
src/store/pptWork.ts

@@ -1,6 +1,6 @@
 import { defineStore } from "pinia"
 import { store } from "./index"
-import { getTeacherChapterKnowledgeMaterial, putTeacherChapterKnowledgeMaterial } from "@/api/pptOperate"
+import { getTeacherChapterKnowledgeMaterial, putTeacherChapterKnowledgeMaterial, putChapterKnowledgeMaterialUpdate } from "@/api/pptOperate"
 import { httpAjaxErrMsg } from "@/plugins/httpAjax"
 import { useRoute } from "vue-router"
 import LoadingBar from "@/plugins/loadingBar"
@@ -10,6 +10,7 @@ import { ElMessage } from "element-plus"
 import { toBlob } from "html-to-image"
 import { useSlidesStore } from "@/store"
 import queryParams, { initQueryParams } from "@/queryParams"
+import router from "@/router"
 
 type pptWork = { id: string; coverImg: string; jsonUrl: string; isSave: boolean }
 
@@ -42,6 +43,9 @@ const useStore = defineStore("pptWork", {
               jsonToPpt(jsonRes.data)
             }
           }
+        } else {
+          // 获取不到ppt数据的时候 跳转到错误页面
+          router.replace("/err")
         }
         LoadingBar.loading(false)
       }
@@ -52,11 +56,12 @@ const useStore = defineStore("pptWork", {
       const { blob } = getJsonToBlob()
       fileUpload(`${this.id}ppt`, blob, `${this.id}/`, false, { isLoading: false })
         .then(url => {
-          httpAjaxErrMsg(putTeacherChapterKnowledgeMaterial, {
+          const _time = Date.now()
+          httpAjaxErrMsg(queryParams.fromType === "PLATFORM" ? putChapterKnowledgeMaterialUpdate : putTeacherChapterKnowledgeMaterial, {
             id: this.id,
             dataJson: JSON.stringify({
-              coverImg: this.coverImg,
-              jsonUrl: url
+              coverImg: this.coverImg + `?v=_${_time}`, // 加上时间戳,防止资源更新之后的缓存
+              jsonUrl: url + `?v=_${_time}`
             })
           }).then(res => {
             if (res.code === 200) {

+ 3 - 0
src/types/slides.ts

@@ -625,11 +625,14 @@ export interface PPTAudioElement extends PPTBaseElement {
  * subtype: elf-sing-play
  *
  * sid: 曲子id
+ *
+ * title:曲目名称
  */
 export interface PPTCloudCoachElement extends PPTBaseElement {
   type: "elf"
   subtype: "elf-sing-play"
   sid: string
+  title: string
 }
 
 export type PPTElement =

+ 3 - 23
src/views/Editor/CanvasTool/index.vue

@@ -225,7 +225,7 @@
         "
       />
     </Modal>
-    <!--<Modal
+    <Modal
       :contentStyle="{
         width: '70%',
         minWidth: '1200px',
@@ -245,26 +245,6 @@
           }
         "
       />
-    </Modal> -->
-    <Modal
-      :contentStyle="{
-        width: '800px',
-        height: '600px',
-        boxShadow: '0px 2px 10px 0px rgba(0,0,0,0.08)',
-        borderRadius: '16px',
-        border: '1px solid #DEDEDE',
-        padding: '0'
-      }"
-      v-model:visible="cloudCoachVisible"
-    >
-      <cloudCoachList
-        @update="handleCloudCoach"
-        @close="
-          () => {
-            cloudCoachVisible = false
-          }
-        "
-      />
     </Modal>
   </div>
 </template>
@@ -361,8 +341,8 @@ function handleUpload(fileData: UploadRequestOptions) {
 }
 
 // 处理云教练创建
-function handleCloudCoach(id: string) {
-  createCloudCoachElement(id)
+function handleCloudCoach(id: string, name: string) {
+  createCloudCoachElement(id, name)
   cloudCoachVisible.value = false
 }
 

+ 1 - 1
src/views/Editor/EditorHeader/index.vue

@@ -46,7 +46,7 @@
     </div>
 
     <div class="right">
-      <div class="cancelBtn" @click="handleClose">取消</div>
+      <div class="cancelBtn" @click="handleClose">退出</div>
       <div class="saveBtn" @click="handleSave">保存课件</div>
     </div>
 

+ 11 - 1
src/views/Editor/Thumbnails/index.vue

@@ -86,7 +86,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, nextTick, ref, watch } from "vue"
+import { computed, nextTick, onMounted, ref, watch } from "vue"
 import { storeToRefs } from "pinia"
 import { useMainStore, useSlidesStore, useKeyboardStore } from "@/store"
 import { fillDigit } from "@/utils/common"
@@ -146,6 +146,16 @@ watch(
   }
 )
 
+// 从预览切换回来的时候 滚动到对应的位置
+onMounted(() => {
+  const activeThumbnailRef: HTMLElement = thumbnailsRef.value?.$el?.querySelector(".thumbnail-item.active")
+  if (thumbnailsRef.value && activeThumbnailRef && !isElementInViewport(activeThumbnailRef, thumbnailsRef.value.$el)) {
+    setTimeout(() => {
+      activeThumbnailRef.scrollIntoView()
+    }, 100)
+  }
+})
+
 // 切换页面
 const changeSlideIndex = (index: number) => {
   mainStore.setActiveElementIdList([])

+ 4 - 9
src/views/Screen/BaseView.vue

@@ -157,10 +157,10 @@ const contextmenus = (): ContextmenuItem[] => {
       handler: () => setLoopPlay(!loopPlay.value)
     },
     { divider: true },
-    {
-      text: "显示工具栏",
-      handler: () => (rightToolsVisible.value = true)
-    },
+    // {
+    //   text: "显示工具栏",
+    //   handler: () => (rightToolsVisible.value = true)
+    // },
     {
       text: "查看所有幻灯片",
       handler: () => (slideThumbnailModelVisible.value = true)
@@ -184,11 +184,6 @@ const contextmenus = (): ContextmenuItem[] => {
   }
   return menusData
 }
-
-defineExpose({
-  execPrev,
-  execNext
-})
 </script>
 
 <style lang="scss" scoped>

+ 2 - 17
src/views/Screen/PresenterView.vue

@@ -104,18 +104,8 @@ const timerlVisible = ref(false)
 const laserPen = ref(false)
 const screenStore = useScreenStore()
 
-const {
-  mousewheelListener,
-  touchStartListener,
-  touchEndListener,
-  turnPrevSlide,
-  turnNextSlide,
-  turnSlideToIndex,
-  turnSlideToId,
-  animationIndex,
-  execPrev,
-  execNext
-} = useExecPlay()
+const { mousewheelListener, touchStartListener, touchEndListener, turnPrevSlide, turnNextSlide, turnSlideToIndex, turnSlideToId, animationIndex } =
+  useExecPlay()
 
 const { slideWidth, slideHeight } = useSlideSize(slideListWrapRef)
 const { exitScreening } = useScreening()
@@ -194,11 +184,6 @@ const contextmenus = (): ContextmenuItem[] => {
   }
   return menusData
 }
-
-defineExpose({
-  execPrev,
-  execNext
-})
 </script>
 
 <style lang="scss" scoped>

+ 1 - 1
src/views/Screen/ScreenElement.vue

@@ -13,7 +13,7 @@
     :title="elementInfo.link?.target || ''"
     @click="$event => openLink($event)"
   >
-    <component :is="currentElementComponent" :elementInfo="elementInfo"></component>
+    <component :is="currentElementComponent" :needWaitAnimation="needWaitAnimation" :elementInfo="elementInfo" />
   </div>
 </template>
 

+ 54 - 52
src/views/Screen/hooks/useExecPlay.ts

@@ -1,10 +1,11 @@
-import { onMounted, onUnmounted, ref } from 'vue'
-import { throttle } from 'lodash'
-import { storeToRefs } from 'pinia'
-import { useSlidesStore } from '@/store'
-import { KEYS } from '@/configs/hotkey'
-import { ANIMATION_CLASS_PREFIX } from '@/configs/animation'
-import message from '@/utils/message'
+import { onMounted, onUnmounted, ref } from "vue"
+import { throttle } from "lodash"
+import { storeToRefs } from "pinia"
+import { useSlidesStore, useScreenStore } from "@/store"
+import { KEYS } from "@/configs/hotkey"
+import { ANIMATION_CLASS_PREFIX } from "@/configs/animation"
+import message from "@/utils/message"
+import { changePageSlideMes } from "@/messageHooks/pptScreen"
 
 export default () => {
   const slidesStore = useSlidesStore()
@@ -41,21 +42,21 @@ export default () => {
       }
 
       const animationName = `${ANIMATION_CLASS_PREFIX}${animation.effect}`
-      
+
       // 执行动画前先清除原有的动画状态(如果有)
-      elRef.style.removeProperty('--animate-duration')
+      elRef.style.removeProperty("--animate-duration")
       for (const classname of elRef.classList) {
         if (classname.indexOf(ANIMATION_CLASS_PREFIX) !== -1) elRef.classList.remove(classname, `${ANIMATION_CLASS_PREFIX}animated`)
       }
-      
+
       // 执行动画
-      elRef.style.setProperty('--animate-duration', `${animation.duration}ms`)
+      elRef.style.setProperty("--animate-duration", `${animation.duration}ms`)
       elRef.classList.add(animationName, `${ANIMATION_CLASS_PREFIX}animated`)
 
       // 执行动画结束,将“退场”以外的动画状态清除
       const handleAnimationEnd = () => {
-        if (animation.type !== 'out') {
-          elRef.style.removeProperty('--animate-duration')
+        if (animation.type !== "out") {
+          elRef.style.removeProperty("--animate-duration")
           elRef.classList.remove(animationName, `${ANIMATION_CLASS_PREFIX}animated`)
         }
 
@@ -66,14 +67,14 @@ export default () => {
           if (autoNext) runAnimation()
         }
       }
-      elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
+      elRef.addEventListener("animationend", handleAnimationEnd, { once: true })
     }
   }
 
   onMounted(() => {
     const firstAnimations = formatedAnimations.value[0]
     if (firstAnimations && firstAnimations.animations.length) {
-      const autoExecFirstAnimations = firstAnimations.animations.every(item => item.trigger === 'auto' || item.trigger === 'meantime')
+      const autoExecFirstAnimations = firstAnimations.animations.every(item => item.trigger === "auto" || item.trigger === "meantime")
       if (autoExecFirstAnimations) runAnimation()
     }
   })
@@ -86,15 +87,15 @@ export default () => {
     for (const animation of animations) {
       const elRef: HTMLElement | null = document.querySelector(`#screen-element-${animation.elId} [class^=base-element-]`)
       if (!elRef) continue
-      
-      elRef.style.removeProperty('--animate-duration')
+
+      elRef.style.removeProperty("--animate-duration")
       for (const classname of elRef.classList) {
         if (classname.indexOf(ANIMATION_CLASS_PREFIX) !== -1) elRef.classList.remove(classname, `${ANIMATION_CLASS_PREFIX}animated`)
       }
     }
 
     // 如果撤销时该位置有且仅有强调动画,则继续执行一次撤销
-    if (animations.every(item => item.type === 'attention')) execPrev()
+    if (animations.every(item => item.type === "attention")) execPrev()
   }
 
   // 关闭自动播放
@@ -113,9 +114,13 @@ export default () => {
     loopPlay.value = loop
   }
 
-  const throttleMassage = throttle(function(msg) {
-    message.success(msg)
-  }, 1000, { leading: true, trailing: false })
+  const throttleMassage = throttle(
+    function (msg) {
+      message.success(msg)
+    },
+    1000,
+    { leading: true, trailing: false }
+  )
 
   // 向上/向下播放
   // 遇到元素动画时,优先执行动画播放,无动画则执行翻页
@@ -124,34 +129,29 @@ export default () => {
   const execPrev = () => {
     if (formatedAnimations.value.length && animationIndex.value > 0) {
       revokeAnimation()
-    }
-    else if (slideIndex.value > 0) {
+    } else if (slideIndex.value > 0) {
       slidesStore.updateSlideIndex(slideIndex.value - 1)
       if (slideIndex.value < playedSlidesMinIndex.value) {
         animationIndex.value = 0
         playedSlidesMinIndex.value = slideIndex.value
-      }
-      else animationIndex.value = formatedAnimations.value.length
-    }
-    else {
+      } else animationIndex.value = formatedAnimations.value.length
+    } else {
       if (loopPlay.value) turnSlideToIndex(slides.value.length - 1)
-      else throttleMassage('已经是第一页了')
+      else throttleMassage("已经是第一页了")
     }
     inAnimation.value = false
   }
   const execNext = () => {
     if (formatedAnimations.value.length && animationIndex.value < formatedAnimations.value.length) {
       runAnimation()
-    }
-    else if (slideIndex.value < slides.value.length - 1) {
+    } else if (slideIndex.value < slides.value.length - 1) {
       slidesStore.updateSlideIndex(slideIndex.value + 1)
       animationIndex.value = 0
       inAnimation.value = false
-    }
-    else {
+    } else {
       if (loopPlay.value) turnSlideToIndex(0)
       else {
-        throttleMassage('已经是最后一页了')
+        throttleMassage("已经是最后一页了")
         closeAutoPlay()
       }
       inAnimation.value = false
@@ -162,7 +162,7 @@ export default () => {
   const autoPlayInterval = ref(2500)
   const autoPlay = () => {
     closeAutoPlay()
-    message.success('开始自动放映')
+    message.success("开始自动放映")
     autoPlayTimer.value = setInterval(execNext, autoPlayInterval.value)
   }
 
@@ -173,18 +173,22 @@ export default () => {
   }
 
   // 鼠标滚动翻页
-  const mousewheelListener = throttle(function(e: WheelEvent) {
-    if (e.deltaY < 0) execPrev()
-    else if (e.deltaY > 0) execNext()
-  }, 500, { leading: true, trailing: false })
+  const mousewheelListener = throttle(
+    function (e: WheelEvent) {
+      if (e.deltaY < 0) execPrev()
+      else if (e.deltaY > 0) execNext()
+    },
+    500,
+    { leading: true, trailing: false }
+  )
 
   // 触摸屏上下滑动翻页
-  const touchInfo = ref<{ x: number; y: number; } | null>(null)
+  const touchInfo = ref<{ x: number; y: number } | null>(null)
 
   const touchStartListener = (e: TouchEvent) => {
     touchInfo.value = {
       x: e.changedTouches[0].pageX,
-      y: e.changedTouches[0].pageY,
+      y: e.changedTouches[0].pageY
     }
   }
   const touchEndListener = (e: TouchEvent) => {
@@ -193,7 +197,7 @@ export default () => {
     const offsetX = Math.abs(touchInfo.value.x - e.changedTouches[0].pageX)
     const offsetY = e.changedTouches[0].pageY - touchInfo.value.y
 
-    if ( Math.abs(offsetY) > offsetX && Math.abs(offsetY) > 50 ) {
+    if (Math.abs(offsetY) > offsetX && Math.abs(offsetY) > 50) {
       touchInfo.value = null
 
       if (offsetY > 0) execPrev()
@@ -206,17 +210,11 @@ export default () => {
     const key = e.key.toUpperCase()
 
     if (key === KEYS.UP || key === KEYS.LEFT || key === KEYS.PAGEUP) execPrev()
-    else if (
-      key === KEYS.DOWN || 
-      key === KEYS.RIGHT ||
-      key === KEYS.SPACE || 
-      key === KEYS.ENTER ||
-      key === KEYS.PAGEDOWN
-    ) execNext()
+    else if (key === KEYS.DOWN || key === KEYS.RIGHT || key === KEYS.SPACE || key === KEYS.ENTER || key === KEYS.PAGEDOWN) execNext()
   }
 
-  onMounted(() => document.addEventListener('keydown', keydownListener))
-  onUnmounted(() => document.removeEventListener('keydown', keydownListener))
+  onMounted(() => document.addEventListener("keydown", keydownListener))
+  onUnmounted(() => document.removeEventListener("keydown", keydownListener))
 
   // 切换到上一张/上一张幻灯片(无视元素的入场动画)
   const turnPrevSlide = () => {
@@ -240,7 +238,11 @@ export default () => {
       animationIndex.value = 0
     }
   }
-
+  const screenStore = useScreenStore()
+  if (["pptScreen", "mobileScreen"].includes(screenStore.mode)) {
+    // mes 翻页
+    changePageSlideMes(execPrev, execNext, animationIndex, formatedAnimations)
+  }
   return {
     autoPlayTimer,
     autoPlayInterval,
@@ -258,6 +260,6 @@ export default () => {
     turnSlideToId,
     execPrev,
     execNext,
-    animationIndex,
+    animationIndex
   }
 }

+ 2 - 14
src/views/Screen/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="pptist-screen">
-    <BaseView ref="screenViewDom" :changeViewMode="changeViewMode" v-if="viewMode === 'base'" />
-    <PresenterView ref="screenViewDom" :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
+    <BaseView :changeViewMode="changeViewMode" v-if="viewMode === 'base'" />
+    <PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
   </div>
 </template>
 
@@ -12,7 +12,6 @@ import useScreening from "@/hooks/useScreening"
 
 import BaseView from "./BaseView.vue"
 import PresenterView from "./PresenterView.vue"
-import { changePageSlideMes } from "@/messageHooks/pptScreen"
 
 const viewMode = ref<"base" | "presenter">("base")
 
@@ -22,17 +21,6 @@ const changeViewMode = (mode: "base" | "presenter") => {
 
 const { exitScreening } = useScreening()
 
-const screenViewDom = ref()
-// mes 翻页
-changePageSlideMes((type: "prev" | "next") => {
-  /*  翻页 */
-  if (type === "prev") {
-    screenViewDom.value.execPrev()
-  } else if (type === "next") {
-    screenViewDom.value.execNext()
-  }
-})
-
 // 快捷键退出放映
 const keydownListener = (e: KeyboardEvent) => {
   const key = e.key.toUpperCase()

+ 66 - 55
src/views/components/element/AudioElement/AudioPlayer.vue

@@ -1,13 +1,9 @@
 <template>
-  <div 
-    class="audio-player"
-    :style="{ transform: `scale(${1 / scale})` }"
-  >
+  <div class="audio-player" :style="{ transform: `scale(${1 / scale})` }">
     <audio
       class="audio"
       ref="audioRef"
       :src="src"
-      :autoplay="autoplay"
       @durationchange="handleDurationchange()"
       @timeupdate="handleTimeupdate()"
       @play="handlePlayed()"
@@ -48,10 +44,10 @@
       </div>
 
       <span class="time">
-        <span class="ptime">{{ptime}}</span> / <span class="dtime">{{dtime}}</span>
+        <span class="ptime">{{ ptime }}</span> / <span class="dtime">{{ dtime }}</span>
       </span>
 
-      <div 
+      <div
         class="bar-wrap"
         ref="playBarWrap"
         @mousedown="handleMousedownPlayBar()"
@@ -60,7 +56,7 @@
         @mouseenter="playBarTimeVisible = true"
         @mouseleave="playBarTimeVisible = false"
       >
-        <div class="bar-time" :class="{ 'hidden': !playBarTimeVisible }" :style="{ left: playBarTimeLeft }">{{playBarTime}}</div>
+        <div class="bar-time" :class="{ hidden: !playBarTimeVisible }" :style="{ left: playBarTimeLeft }">{{ playBarTime }}</div>
         <div class="bar">
           <div class="loaded" :style="{ width: loadedBarWidth }"></div>
           <div class="played" :style="{ width: playedBarWidth }">
@@ -73,27 +69,44 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue'
-import message from '@/utils/message'
-
-const props = withDefaults(defineProps<{
-  src: string
-  loop: boolean
-  autoplay?: boolean
-  scale?: number
-}>(), {
-  autoplay: false,
-  scale: 1,
-})
+import { computed, ref, watch } from "vue"
+import message from "@/utils/message"
+
+const props = withDefaults(
+  defineProps<{
+    src: string
+    loop: boolean
+    autoplay?: boolean
+    scale?: number
+    needWaitAnimation?: boolean
+  }>(),
+  {
+    autoplay: false,
+    scale: 1
+  }
+)
+
+watch(
+  () => props.needWaitAnimation,
+  () => {
+    if (props.autoplay) {
+      if (!props.needWaitAnimation) {
+        play()
+      } else {
+        pause()
+      }
+    }
+  }
+)
 
 const secondToTime = (second = 0) => {
-  if (second === 0 || isNaN(second)) return '00:00'
+  if (second === 0 || isNaN(second)) return "00:00"
 
-  const add0 = (num: number) => (num < 10 ? '0' + num : '' + num)
+  const add0 = (num: number) => (num < 10 ? "0" + num : "" + num)
   const hour = Math.floor(second / 3600)
   const min = Math.floor((second - hour * 3600) / 60)
   const sec = Math.floor(second - hour * 3600 - min * 60)
-  return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':')
+  return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(":")
 }
 
 const getBoundingClientRectViewLeft = (element: HTMLElement) => {
@@ -111,14 +124,14 @@ const duration = ref(0)
 const loaded = ref(0)
 
 const playBarTimeVisible = ref(false)
-const playBarTime = ref('00:00')
-const playBarTimeLeft = ref('0')
+const playBarTime = ref("00:00")
+const playBarTimeLeft = ref("0")
 
 const ptime = computed(() => secondToTime(currentTime.value))
 const dtime = computed(() => secondToTime(duration.value))
-const playedBarWidth = computed(() => currentTime.value / duration.value * 100 + '%')
-const loadedBarWidth = computed(() => loaded.value / duration.value * 100 + '%')
-const volumeBarWidth = computed(() => volume.value * 100 + '%')
+const playedBarWidth = computed(() => (currentTime.value / duration.value) * 100 + "%")
+const loadedBarWidth = computed(() => (loaded.value / duration.value) * 100 + "%")
+const volumeBarWidth = computed(() => volume.value * 100 + "%")
 
 const seek = (time: number) => {
   if (!audioRef.value) return
@@ -145,7 +158,7 @@ const pause = () => {
 }
 
 const toggle = () => {
-  if (paused.value) play() 
+  if (paused.value) play()
   else pause()
 }
 
@@ -184,11 +197,11 @@ const handleProgress = () => {
   loaded.value = audioRef.value?.buffered.length ? audioRef.value.buffered.end(audioRef.value.buffered.length - 1) : 0
 }
 
-const handleError = () => message.error('视频加载失败')
+const handleError = () => message.error("视频加载失败")
 
 const thumbMove = (e: MouseEvent | TouchEvent) => {
   if (!audioRef.value || !playBarWrap.value) return
-  const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX
+  const clientX = "clientX" in e ? e.clientX : e.changedTouches[0].clientX
   let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth
   percentage = Math.max(percentage, 0)
   percentage = Math.min(percentage, 1)
@@ -201,7 +214,7 @@ const thumbMove = (e: MouseEvent | TouchEvent) => {
 const thumbUp = (e: MouseEvent | TouchEvent) => {
   if (!audioRef.value || !playBarWrap.value) return
 
-  const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX
+  const clientX = "clientX" in e ? e.clientX : e.changedTouches[0].clientX
   let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth
   percentage = Math.max(percentage, 0)
   percentage = Math.min(percentage, 1)
@@ -210,38 +223,38 @@ const thumbUp = (e: MouseEvent | TouchEvent) => {
   audioRef.value.currentTime = time
   currentTime.value = time
 
-  document.removeEventListener('mousemove', thumbMove)
-  document.removeEventListener('touchmove', thumbMove)
-  document.removeEventListener('mouseup', thumbUp)
-  document.removeEventListener('touchend', thumbUp)
+  document.removeEventListener("mousemove", thumbMove)
+  document.removeEventListener("touchmove", thumbMove)
+  document.removeEventListener("mouseup", thumbUp)
+  document.removeEventListener("touchend", thumbUp)
 }
 
 const handleMousedownPlayBar = () => {
-  document.addEventListener('mousemove', thumbMove)
-  document.addEventListener('touchmove', thumbMove)
-  document.addEventListener('mouseup', thumbUp)
-  document.addEventListener('touchend', thumbUp)
+  document.addEventListener("mousemove", thumbMove)
+  document.addEventListener("touchmove", thumbMove)
+  document.addEventListener("mouseup", thumbUp)
+  document.addEventListener("touchend", thumbUp)
 }
 
 const volumeMove = (e: MouseEvent | TouchEvent) => {
   if (!volumeBarRef.value) return
-  const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX
+  const clientX = "clientX" in e ? e.clientX : e.changedTouches[0].clientX
   const percentage = (clientX - getBoundingClientRectViewLeft(volumeBarRef.value)) / 45
   setVolume(percentage)
 }
 
 const volumeUp = () => {
-  document.removeEventListener('mousemove', volumeMove)
-  document.removeEventListener('touchmove', volumeMove)
-  document.removeEventListener('mouseup', volumeUp)
-  document.removeEventListener('touchend', volumeUp)
+  document.removeEventListener("mousemove", volumeMove)
+  document.removeEventListener("touchmove", volumeMove)
+  document.removeEventListener("mouseup", volumeUp)
+  document.removeEventListener("touchend", volumeUp)
 }
 
 const handleMousedownVolumeBar = () => {
-  document.addEventListener('mousemove', volumeMove)
-  document.addEventListener('touchmove', volumeMove)
-  document.addEventListener('mouseup', volumeUp)
-  document.addEventListener('touchend', volumeUp)
+  document.addEventListener("mousemove", volumeMove)
+  document.addEventListener("touchmove", volumeMove)
+  document.addEventListener("mouseup", volumeUp)
+  document.addEventListener("touchend", volumeUp)
 }
 
 const handleClickVolumeBar = (e: MouseEvent) => {
@@ -269,20 +282,18 @@ const toggleVolume = () => {
   if (audioRef.value.muted) {
     audioRef.value.muted = false
     setVolume(0.5)
-  }
-  else {
+  } else {
     audioRef.value.muted = true
     setVolume(0)
   }
 }
 
 defineExpose({
-  toggle,
+  toggle
 })
 </script>
 
 <style scoped lang="scss">
-
 .audio-player {
   width: 280px;
   height: 50px;
@@ -401,7 +412,7 @@ defineExpose({
       }
 
       .icon-content {
-        transition: all .2s ease-in-out;
+        transition: all 0.2s ease-in-out;
         opacity: 0.8;
         color: #fff;
       }
@@ -500,4 +511,4 @@ defineExpose({
     }
   }
 }
-</style>
+</style>

+ 2 - 0
src/views/components/element/AudioElement/ScreenAudioElement.vue

@@ -27,6 +27,7 @@
           :loop="elementInfo.loop"
           :autoplay="elementInfo.autoplay"
           :scale="scale"
+          :needWaitAnimation="needWaitAnimation"
         />
       </div>
     </div>
@@ -44,6 +45,7 @@ import AudioPlayer from "./AudioPlayer.vue"
 
 const props = defineProps<{
   elementInfo: PPTAudioElement
+  needWaitAnimation: boolean
 }>()
 
 const { viewportRatio, currentSlide, viewportSize } = storeToRefs(useSlidesStore())

+ 16 - 16
src/views/components/element/VideoElement/ScreenVideoElement.vue

@@ -1,25 +1,24 @@
 <template>
-  <div class="base-element-video screen-element-video"
+  <div
+    class="base-element-video screen-element-video"
     :style="{
       top: elementInfo.top + 'px',
       left: elementInfo.left + 'px',
       width: elementInfo.width + 'px',
-      height: elementInfo.height + 'px',
+      height: elementInfo.height + 'px'
     }"
   >
-    <div
-      class="rotate-wrapper"
-      :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
-    >
+    <div class="rotate-wrapper" :style="{ transform: `rotate(${elementInfo.rotate}deg)` }">
       <div class="element-content">
         <VideoPlayer
           v-if="inCurrentSlide"
           :width="elementInfo.width"
           :height="elementInfo.height"
-          :src="elementInfo.src" 
-          :poster="elementInfo.poster"  
+          :src="elementInfo.src"
+          :poster="elementInfo.poster"
           :autoplay="elementInfo.autoplay"
-          :scale="scale" 
+          :scale="scale"
+          :needWaitAnimation="needWaitAnimation"
         />
       </div>
     </div>
@@ -27,22 +26,23 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useSlidesStore } from '@/store'
-import type { PPTVideoElement } from '@/types/slides'
-import { injectKeySlideId, injectKeySlideScale } from '@/types/injectKey'
+import { computed, inject, ref } from "vue"
+import { storeToRefs } from "pinia"
+import { useSlidesStore } from "@/store"
+import type { PPTVideoElement } from "@/types/slides"
+import { injectKeySlideId, injectKeySlideScale } from "@/types/injectKey"
 
-import VideoPlayer from './VideoPlayer/index.vue'
+import VideoPlayer from "./VideoPlayer/index.vue"
 
 defineProps<{
   elementInfo: PPTVideoElement
+  needWaitAnimation: boolean
 }>()
 
 const { currentSlide } = storeToRefs(useSlidesStore())
 
 const scale = inject(injectKeySlideScale) || ref(1)
-const slideId = inject(injectKeySlideId) || ref('')
+const slideId = inject(injectKeySlideId) || ref("")
 
 const inCurrentSlide = computed(() => currentSlide.value.id === slideId.value)
 </script>

+ 95 - 72
src/views/components/element/VideoElement/VideoPlayer/index.vue

@@ -1,11 +1,11 @@
 <template>
-  <div 
+  <div
     class="video-player"
-    :class="{ 'hide-controller': hideController }" 
+    :class="{ 'hide-controller': hideController }"
     :style="{
       width: width * scale + 'px',
       height: height * scale + 'px',
-      transform: `scale(${1 / scale})`,
+      transform: `scale(${1 / scale})`
     }"
     @mousemove="autoHideController()"
     @click="autoHideController()"
@@ -17,7 +17,6 @@
         class="video"
         ref="videoRef"
         :src="src"
-        :autoplay="autoplay"
         :poster="poster"
         webkit-playsinline
         playsinline
@@ -25,7 +24,12 @@
         @timeupdate="handleTimeupdate()"
         @ended="handleEnded()"
         @progress="handleProgress()"
-        @play="autoHideController(); paused = false"
+        @play="
+          () => {
+            autoHideController()
+            paused = false
+          }
+        "
         @pause="autoHideController()"
         @error="handleError()"
       ></video>
@@ -68,33 +72,35 @@
           </div>
         </div>
         <span class="time">
-          <span class="ptime">{{ptime}}</span> / <span class="dtime">{{dtime}}</span>
+          <span class="ptime">{{ ptime }}</span> / <span class="dtime">{{ dtime }}</span>
         </span>
       </div>
 
       <div class="icons icons-right">
         <div class="speed">
           <div class="icon speed-icon">
-            <span class="icon-content" @click="speedMenuVisible = !speedMenuVisible">{{playbackRate === 1 ? '倍速' : (playbackRate + 'x')}}</span>
+            <span class="icon-content" @click="speedMenuVisible = !speedMenuVisible">{{ playbackRate === 1 ? "倍速" : playbackRate + "x" }}</span>
             <div class="speed-menu" v-if="speedMenuVisible" @mouseleave="speedMenuVisible = false">
-              <div 
-                class="speed-menu-item" 
-                :class="{ 'active': item.value === playbackRate }"
-                v-for="item in speedOptions" 
-                :key="item.label" 
+              <div
+                class="speed-menu-item"
+                :class="{ active: item.value === playbackRate }"
+                v-for="item in speedOptions"
+                :key="item.label"
                 @click="speed(item.value)"
-              >{{item.label}}</div>
+              >
+                {{ item.label }}
+              </div>
             </div>
           </div>
         </div>
         <div class="loop" @click="toggleLoop()">
-          <div class="icon loop-icon" :class="{ 'active': loop }">
-            <span class="icon-content">循环{{loop ? '开' : '关'}}</span>
+          <div class="icon loop-icon" :class="{ active: loop }">
+            <span class="icon-content">循环{{ loop ? "开" : "关" }}</span>
           </div>
         </div>
       </div>
 
-      <div 
+      <div
         class="bar-wrap"
         ref="playBarWrap"
         @mousedown="handleMousedownPlayBar()"
@@ -103,7 +109,7 @@
         @mouseenter="playBarTimeVisible = true"
         @mouseleave="playBarTimeVisible = false"
       >
-        <div class="bar-time" :class="{ 'hidden': !playBarTimeVisible }" :style="{ left: playBarTimeLeft }">{{playBarTime}}</div>
+        <div class="bar-time" :class="{ hidden: !playBarTimeVisible }" :style="{ left: playBarTimeLeft }">{{ playBarTime }}</div>
         <div class="bar">
           <div class="loaded" :style="{ width: loadedBarWidth }"></div>
           <div class="played" :style="{ width: playedBarWidth }">
@@ -116,30 +122,47 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue'
-import useMSE from './useMSE'
-
-const props = withDefaults(defineProps<{
-  width: number
-  height: number
-  src: string
-  poster?: string
-  autoplay?: boolean
-  scale?: number
-}>(), {
-  poster: '',
-  autoplay: false,
-  scale: 1,
-})
+import { computed, ref, watch } from "vue"
+import useMSE from "./useMSE"
+
+const props = withDefaults(
+  defineProps<{
+    width: number
+    height: number
+    src: string
+    poster?: string
+    autoplay?: boolean
+    scale?: number
+    needWaitAnimation?: boolean
+  }>(),
+  {
+    poster: "",
+    autoplay: false,
+    scale: 1
+  }
+)
+
+watch(
+  () => props.needWaitAnimation,
+  () => {
+    if (props.autoplay) {
+      if (!props.needWaitAnimation) {
+        play()
+      } else {
+        pause()
+      }
+    }
+  }
+)
 
 const secondToTime = (second = 0) => {
-  if (second === 0 || isNaN(second)) return '00:00'
+  if (second === 0 || isNaN(second)) return "00:00"
 
-  const add0 = (num: number) => (num < 10 ? '0' + num : '' + num)
+  const add0 = (num: number) => (num < 10 ? "0" + num : "" + num)
   const hour = Math.floor(second / 3600)
   const min = Math.floor((second - hour * 3600) / 60)
   const sec = Math.floor(second - hour * 3600 - min * 60)
-  return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':')
+  return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(":")
 }
 
 const getBoundingClientRectViewLeft = (element: HTMLElement) => {
@@ -160,23 +183,23 @@ const bezelTransition = ref(false)
 const playbackRate = ref(1)
 
 const playBarTimeVisible = ref(false)
-const playBarTime = ref('00:00')
-const playBarTimeLeft = ref('0')
+const playBarTime = ref("00:00")
+const playBarTimeLeft = ref("0")
 
 const ptime = computed(() => secondToTime(currentTime.value))
 const dtime = computed(() => secondToTime(duration.value))
-const playedBarWidth = computed(() => currentTime.value / duration.value * 100 + '%')
-const loadedBarWidth = computed(() => loaded.value / duration.value * 100 + '%')
-const volumeBarWidth = computed(() => volume.value * 100 + '%')
+const playedBarWidth = computed(() => (currentTime.value / duration.value) * 100 + "%")
+const loadedBarWidth = computed(() => (loaded.value / duration.value) * 100 + "%")
+const volumeBarWidth = computed(() => volume.value * 100 + "%")
 
 const speedMenuVisible = ref(false)
 const speedOptions = [
-  { label: '2x', value: 2 },
-  { label: '1.5x', value: 1.5 },
-  { label: '1.25x', value: 1.25 },
-  { label: '1x', value: 1 },
-  { label: '0.75x', value: 0.75 },
-  { label: '0.5x', value: 0.5 },
+  { label: "2x", value: 2 },
+  { label: "1.5x", value: 1.5 },
+  { label: "1.25x", value: 1.25 },
+  { label: "1x", value: 1 },
+  { label: "0.75x", value: 0.75 },
+  { label: "0.5x", value: 0.5 }
 ]
 
 const seek = (time: number) => {
@@ -206,7 +229,7 @@ const pause = () => {
 }
 
 const toggle = () => {
-  if (paused.value) play() 
+  if (paused.value) play()
   else pause()
 }
 
@@ -247,11 +270,11 @@ const handleProgress = () => {
 }
 
 const loadError = ref(false)
-const handleError = () => loadError.value = true
+const handleError = () => (loadError.value = true)
 
 const thumbMove = (e: MouseEvent | TouchEvent) => {
   if (!videoRef.value || !playBarWrap.value) return
-  const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX
+  const clientX = "clientX" in e ? e.clientX : e.changedTouches[0].clientX
   let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth
   percentage = Math.max(percentage, 0)
   percentage = Math.min(percentage, 1)
@@ -264,7 +287,7 @@ const thumbMove = (e: MouseEvent | TouchEvent) => {
 const thumbUp = (e: MouseEvent | TouchEvent) => {
   if (!videoRef.value || !playBarWrap.value) return
 
-  const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX
+  const clientX = "clientX" in e ? e.clientX : e.changedTouches[0].clientX
   let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth
   percentage = Math.max(percentage, 0)
   percentage = Math.min(percentage, 1)
@@ -273,38 +296,38 @@ const thumbUp = (e: MouseEvent | TouchEvent) => {
   videoRef.value.currentTime = time
   currentTime.value = time
 
-  document.removeEventListener('mousemove', thumbMove)
-  document.removeEventListener('touchmove', thumbMove)
-  document.removeEventListener('mouseup', thumbUp)
-  document.removeEventListener('touchend', thumbUp)
+  document.removeEventListener("mousemove", thumbMove)
+  document.removeEventListener("touchmove", thumbMove)
+  document.removeEventListener("mouseup", thumbUp)
+  document.removeEventListener("touchend", thumbUp)
 }
 
 const handleMousedownPlayBar = () => {
-  document.addEventListener('mousemove', thumbMove)
-  document.addEventListener('touchmove', thumbMove)
-  document.addEventListener('mouseup', thumbUp)
-  document.addEventListener('touchend', thumbUp)
+  document.addEventListener("mousemove", thumbMove)
+  document.addEventListener("touchmove", thumbMove)
+  document.addEventListener("mouseup", thumbUp)
+  document.addEventListener("touchend", thumbUp)
 }
 
 const volumeMove = (e: MouseEvent | TouchEvent) => {
   if (!volumeBarRef.value) return
-  const clientX = 'clientX' in e ? e.clientX : e.changedTouches[0].clientX
+  const clientX = "clientX" in e ? e.clientX : e.changedTouches[0].clientX
   const percentage = (clientX - getBoundingClientRectViewLeft(volumeBarRef.value)) / 45
   setVolume(percentage)
 }
 
 const volumeUp = () => {
-  document.removeEventListener('mousemove', volumeMove)
-  document.removeEventListener('touchmove', volumeMove)
-  document.removeEventListener('mouseup', volumeUp)
-  document.removeEventListener('touchend', volumeUp)
+  document.removeEventListener("mousemove", volumeMove)
+  document.removeEventListener("touchmove", volumeMove)
+  document.removeEventListener("mouseup", volumeUp)
+  document.removeEventListener("touchend", volumeUp)
 }
 
 const handleMousedownVolumeBar = () => {
-  document.addEventListener('mousemove', volumeMove)
-  document.addEventListener('touchmove', volumeMove)
-  document.addEventListener('mouseup', volumeUp)
-  document.addEventListener('touchend', volumeUp)
+  document.addEventListener("mousemove", volumeMove)
+  document.addEventListener("touchmove", volumeMove)
+  document.addEventListener("mouseup", volumeUp)
+  document.addEventListener("touchend", volumeUp)
 }
 
 const handleClickVolumeBar = (e: MouseEvent) => {
@@ -332,8 +355,7 @@ const toggleVolume = () => {
   if (videoRef.value.muted) {
     videoRef.value.muted = false
     setVolume(0.5)
-  }
-  else {
+  } else {
     videoRef.value.muted = true
     setVolume(0)
   }
@@ -392,7 +414,8 @@ useMSE(props.src, videoRef)
 }
 
 .controller-mask {
-  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==) repeat-x bottom;
+  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==)
+    repeat-x bottom;
   height: 98px;
   width: 100%;
   position: absolute;
@@ -517,7 +540,7 @@ useMSE(props.src, videoRef)
       }
 
       .icon-content {
-        transition: all .2s ease-in-out;
+        transition: all 0.2s ease-in-out;
         opacity: 0.8;
         color: #fff;
       }
@@ -690,4 +713,4 @@ useMSE(props.src, videoRef)
   justify-content: center;
   align-items: center;
 }
-</style>
+</style>

+ 15 - 2
src/views/components/element/cloudCoachElement/BaseCloudCoachElement.vue

@@ -9,17 +9,23 @@
     }"
   >
     <div class="rotate-wrapper" :style="{ transform: `rotate(${elementInfo.rotate}deg)` }">
-      <div class="element-content"></div>
+      <div class="element-content">
+        <div class="title" :style="{ fontSize: 14 / scale + 'px' }">{{ elementInfo.title || "乐谱" }}</div>
+      </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
 import type { PPTCloudCoachElement } from "@/types/slides"
+import { ref, inject } from "vue"
+import { injectKeySlideScale } from "@/types/injectKey"
 
 defineProps<{
   elementInfo: PPTCloudCoachElement
 }>()
+
+const scale = inject(injectKeySlideScale) || ref(1)
 </script>
 
 <style lang="scss" scoped>
@@ -36,7 +42,14 @@ defineProps<{
   display: flex;
   justify-content: center;
   align-items: center;
-  background: url("./cloudCoachList//imgs/musicBg.png") no-repeat;
+  background: url("./cloudCoachList/imgs/musicBg.png") no-repeat;
   background-size: 100% 100%;
+  .title {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    padding: 0 40px;
+    color: #fff;
+  }
 }
 </style>

+ 780 - 50
src/views/components/element/cloudCoachElement/cloudCoachList/cloudCoachList.vue

@@ -1,90 +1,820 @@
 <template>
-  <div class="cloudCoachList" v-loading="loading">
-    <div class="name">乐谱</div>
-    <div class="listCon">
-      <div class="list" v-for="item in listData" :key="item.id">
-        <div>曲目名称:{{ item.name }}</div>
-        <ElButton @click="handleUpdate(item.id)">添加</ElButton>
+  <div class="cloudCoachList">
+    <div class="headCon">
+      <div class="headLeft">
+        <img class="tipImg" src="@/views/Editor/CanvasTool/imgs/yp.png" alt="" />
+        <div class="title">乐谱</div>
+      </div>
+      <div class="headright">
+        <img @click="emits('close')" class="closeBtn" src="./imgs/close.png" alt="" />
       </div>
     </div>
-    <div class="pagination">
-      <el-pagination layout="prev, pager, next" :default-page-size="20" @current-change="handleCurrentChange" :total="paginationData.total" />
+    <div class="content">
+      <div class="tabTools">
+        <div class="tabCon">
+          <div
+            class="tab"
+            @click="handleTabChange(item.value)"
+            :class="{ active: item.value === queryData.sourceType }"
+            v-for="item in tabData"
+            :key="item.value"
+          >
+            {{ item.label }}
+          </div>
+        </div>
+        <div class="query">
+          <Input :placeholder="'请输入搜索关键词'" v-model:value="queryData.name" @enter="handleQuery">
+            <template #prefix>
+              <img class="img" src="./imgs/query.png" alt="" />
+            </template>
+            <template #suffix>
+              <div class="queryBtn" @click="handleQuery">搜索</div>
+            </template>
+          </Input>
+        </div>
+      </div>
+      <div class="musicListCon">
+        <div class="queryFrom" :class="{ isExpandAct: !isExpand }">
+          <div v-show="queryData.sourceType === 2" class="queryFromList">
+            <div class="tit">教程:</div>
+            <div class="queryFromCon">
+              <div
+                v-for="item in musicTagList"
+                :key="item.id"
+                @click="handleMusicTagChange(item.id)"
+                :class="['queryTip', queryData.bookVersionId === item.id && 'active']"
+              >
+                {{ item.name }}
+              </div>
+            </div>
+          </div>
+          <div v-show="queryData.sourceType !== 3" class="queryFromList">
+            <div class="tit">场景:</div>
+            <div class="queryFromCon">
+              <div
+                v-for="item in audioPlayTypesOption"
+                :key="item.value"
+                @click="handleAudioPlayTypesChange(item.value)"
+                :class="['queryTip', queryData.audioPlayTypes === item.value && 'active']"
+              >
+                {{ item.text }}
+              </div>
+            </div>
+          </div>
+          <div v-show="queryData.audioPlayTypes !== 'SING'" class="queryFromList">
+            <div class="tit">乐器:</div>
+            <div class="queryFromCon">
+              <template v-for="item in subjectList">
+                <div
+                  :class="['queryTip', queryData.subject.id === item.instruments[0].id && 'active']"
+                  @click="handleSubjectChange(item.instruments[0])"
+                  v-if="item.instruments.length === 1"
+                  :key="item.id"
+                >
+                  {{ item.instruments[0].name }}
+                </div>
+                <Popover v-model:value="item.isExpand" trigger="mouseenter" v-else :offset="-4" :key="item.id + '_'">
+                  <template #content>
+                    <PopoverMenuItem
+                      @click="
+                        () => {
+                          item.isExpand = false
+                          handleSubjectChange(row)
+                        }
+                      "
+                      v-for="row in item.instruments"
+                      :key="row.id"
+                      :active="row.id === queryData.subject.id"
+                      >{{ row.name }}</PopoverMenuItem
+                    >
+                  </template>
+                  <div class="queryTip" :class="{ hoverActive: isActiveSubjectPop(item) }">
+                    <div>{{ isActiveSubjectPop(item) ? queryData.subject.name : item.name }}</div>
+                    <img src="./imgs/jt.png" alt="" />
+                  </div>
+                </Popover>
+              </template>
+            </div>
+          </div>
+        </div>
+        <div v-show="queryData.sourceType === 2" @click="isExpand = !isExpand" class="isExpand" :class="{ active: isExpand }">
+          <div>{{ isExpand ? "收起" : "展开" }}</div>
+          <img src="./imgs/jiao.png" alt="" />
+        </div>
+        <div class="musicListConBox" v-loading="loading">
+          <div class="musicList" :class="{ empty: !musicList.length && !loading }">
+            <div class="musicListBox" v-if="musicList.length && !loading">
+              <div class="musicCon" v-for="item in musicList" :key="item.id">
+                <div class="musicLeft">
+                  <div class="iconCon">
+                    <img class="icon" :src="item.titleImg" alt="" />
+                    <img v-if="item.sourceFrom === 'PLATFORM'" class="jxImg" src="./imgs/jx.png" alt="" />
+                  </div>
+                  <div class="musicInfo">
+                    <EllipsisScroll class="musicTit" :title="item.name || ''" />
+                    <div class="info">
+                      <div class="hotInfo" v-if="item.usedNum">
+                        <img src="./imgs/hot.png" alt="" />
+                        <div>{{ formatNumber(item.usedNum) }}</div>
+                      </div>
+                      <div class="play" v-if="item.audioPlayTypes?.includes('SING')">演唱</div>
+                      <div class="sing" v-if="item.audioPlayTypes?.includes('PLAY')">演奏</div>
+                      <div class="musicUserName">
+                        <EllipsisScroll :title="item.composer || ''" />
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div class="musicRight">
+                  <img
+                    v-if="queryParams.fromType !== 'PLATFORM'"
+                    class="sc"
+                    @click="handleFavorite(item)"
+                    :src="item.favoriteFlag ? scActImg : scImg"
+                    alt=""
+                  />
+                  <div class="addBtn" @click="handleAddMusic(item.id, item.name)">添加</div>
+                </div>
+              </div>
+            </div>
+            <Empty v-if="!musicList.length && !loading" />
+          </div>
+          <div class="pagination" v-show="musicList.length">
+            <el-pagination layout="prev, pager, next" :default-page-size="21" @current-change="handleCurrentChange" :total="queryData.total" />
+          </div>
+        </div>
+      </div>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ElButton, ElLoading, ElPagination } from "element-plus"
-import { getMaterialQueryPage } from "@/api/pptOperate"
+import { ElLoading, ElPagination } from "element-plus"
+import Input from "@/components/Input.vue"
+import Popover from "@/components/Popover.vue"
+import PopoverMenuItem from "@/components/PopoverMenuItem.vue"
+import Empty from "@/components/Empty"
+import EllipsisScroll from "@/components/ellipsisScroll"
+import { reactive, ref } from "vue"
+import { getMaterialQueryPage, getSubjectListApi, getMusicTagTreeApi, favoriteApi } from "@/api/pptOperate"
 import { httpAjax } from "@/plugins/httpAjax"
-import { ref, reactive } from "vue"
+import queryParams from "@/queryParams"
+import scActImg from "./imgs/scAct.png"
+import scImg from "./imgs/sc.png"
+import { CODE_ERR_CANCELED } from "@/libs/auth"
 
-const emit = defineEmits<{
-  (event: "update", id: string): void
+const emits = defineEmits<{
+  (event: "update", id: string, name: string): void
+  (event: "close"): void
 }>()
-function handleUpdate(id: string) {
-  emit("update", id)
-}
 
-const vLoading = ElLoading.directive
-const loading = ref(false)
+function handleAddMusic(id: string, name: string) {
+  emits("update", id, name)
+  emits("close")
+}
 
-const listData = ref<any[]>([])
+const tabData =
+  queryParams.fromType === "PLATFORM"
+    ? [
+        {
+          label: "共享资源",
+          value: 2
+        }
+      ]
+    : [
+        {
+          label: "相关资源",
+          value: 5
+        },
+        {
+          label: "共享资源",
+          value: 2
+        },
+        {
+          label: "我的资源",
+          value: 3
+        },
+        {
+          label: "我的收藏",
+          value: 4
+        }
+      ]
+// 场景
+const audioPlayTypesOption = [
+  { text: "全部", value: "" },
+  { text: "演唱", value: "SING" },
+  { text: "演奏", value: "PLAY" },
+  { text: "演唱+演奏", value: "PLAY,SING" }
+]
+// 教程
+const musicTagList = ref<any[]>([])
+// 乐器
+const subjectList = ref<any[]>([])
 
-const paginationData = reactive({
+const queryData = reactive({
   page: 1,
-  total: 0
+  rows: 21,
+  total: 0,
+  sourceType: tabData[0].value,
+  name: "",
+  bookVersionId: "",
+  audioPlayTypes: "",
+  subject: {
+    id: "",
+    name: ""
+  }
 })
 
+const musicList = ref<any[]>([])
+const loading = ref(true)
+const vLoading = ElLoading.directive
+const isExpand = ref(true)
+
+getQueryList()
+function getQueryList() {
+  Promise.all([httpAjax(getSubjectListApi), httpAjax(getMusicTagTreeApi)]).then(res => {
+    const [subjectListRes, musicTagTreeRes] = res
+    if (subjectListRes.code === 200) {
+      subjectList.value = subjectListRes.data.map((item: any) => {
+        return item.instruments.length > 1 ? Object.assign(item, { isExpand: ref(false) }) : item
+      })
+      // 赋默认值
+      handleSubjectDefault()
+    }
+    if (musicTagTreeRes.code === 200) {
+      musicTagList.value = [
+        { id: "", name: "全部" },
+        ...musicTagTreeRes.data.map((item: any) => {
+          return {
+            id: item.id,
+            name: item.name
+          }
+        })
+      ]
+    }
+    handleQuery()
+  })
+}
+function handleSubjectDefault() {
+  if (subjectList.value.length > 0) {
+    const instruments = subjectList.value.reduce((arr, item) => {
+      arr.push(...item.instruments)
+      return arr
+    }, [])
+    const instrumentId = queryParams.instrumentId
+    // 有id 就用id,没有就默认第一个
+    const instrumentObj = instrumentId
+      ? instruments.find((i: any) => {
+          return i.id === instrumentId
+        })
+      : instruments[0]
+    if (instrumentObj) {
+      queryData.subject.id = instrumentObj.id
+      queryData.subject.name = instrumentObj.name
+    }
+  }
+}
+function clearQueryData() {
+  queryData.page = 1
+  queryData.rows = 21
+  queryData.total = 0
+  queryData.sourceType = 5
+  queryData.name = ""
+  queryData.bookVersionId = ""
+  queryData.audioPlayTypes = ""
+  queryData.subject = {
+    id: "",
+    name: ""
+  }
+  handleSubjectDefault()
+}
+function handleTabChange(sourceType: number) {
+  clearQueryData()
+  isExpand.value = true
+  queryData.sourceType = sourceType
+  handleQuery()
+}
+function handleMusicTagChange(id: string) {
+  queryData.bookVersionId = id
+  handleQuery()
+}
+function handleAudioPlayTypesChange(value: string) {
+  queryData.audioPlayTypes = value
+  handleQuery()
+}
+function handleSubjectChange(item: any) {
+  queryData.subject.id = item.id
+  queryData.subject.name = item.name
+  handleQuery()
+}
+function isActiveSubjectPop(item: any) {
+  return item.instruments.some((i: any) => {
+    return i.id === queryData.subject.id
+  })
+}
+
 function handleCurrentChange(e: number) {
-  paginationData.page = e
-  queryFun()
+  queryData.page = e
+  handleGetQuery()
+}
+function handleQuery() {
+  queryData.page = 1
+  queryData.rows = 21
+  handleGetQuery()
 }
 
-queryFun()
-function queryFun() {
+let controller: AbortController
+function handleGetQuery() {
   loading.value = true
-  httpAjax(getMaterialQueryPage, {
-    type: "MUSIC",
-    sourceType: 2,
-    enableFlag: true,
-    page: paginationData.page,
-    rows: 20
-  }).then(res => {
+  let { sourceType, subject, audioPlayTypes, name, page, rows, bookVersionId } = queryData
+  let musicalInstrumentId = subject.id
+  const audioPlayTypesParams = audioPlayTypes ? audioPlayTypes.split(",") : []
+  let params: any
+  // 相关资源
+  if (sourceType === 5) {
+    if (audioPlayTypesParams[0] === "SING") {
+      musicalInstrumentId = ""
+    }
+    params = {
+      name,
+      type: "MUSIC",
+      sourceType,
+      musicalInstrumentId,
+      enableFlag: true,
+      page,
+      rows,
+      audioPlayTypes: audioPlayTypesParams,
+      lessonCoursewareKnowledgeId: queryParams.lessonCoursewareKnowledgeId
+    }
+  }
+  if (sourceType === 2) {
+    if (audioPlayTypesParams[0] === "SING") {
+      musicalInstrumentId = ""
+    }
+    params = {
+      name,
+      type: "MUSIC",
+      sourceType,
+      musicalInstrumentId,
+      enableFlag: true,
+      page,
+      rows,
+      audioPlayTypes: audioPlayTypesParams,
+      lessonCoursewareKnowledgeId: queryParams.lessonCoursewareKnowledgeId,
+      bookVersionId
+    }
+  }
+  if (sourceType === 3) {
+    params = {
+      name,
+      type: "MUSIC",
+      sourceType,
+      musicalInstrumentId,
+      enableFlag: true,
+      page,
+      rows
+    }
+  }
+  if (sourceType === 4) {
+    if (audioPlayTypesParams[0] === "SING") {
+      musicalInstrumentId = ""
+    }
+    params = {
+      name,
+      type: "MUSIC",
+      sourceType,
+      musicalInstrumentId,
+      enableFlag: true,
+      page,
+      rows,
+      audioPlayTypes: audioPlayTypesParams
+    }
+  }
+  if (controller) {
+    controller.abort()
+  }
+  controller = new AbortController()
+  httpAjax(getMaterialQueryPage, params, controller).then(res => {
+    // 自己关闭的时候不取消加载
+    if (res.code === CODE_ERR_CANCELED) {
+      return
+    }
+    if (res.code === 200) {
+      musicList.value = res.data.rows.map((item: any) => {
+        item.name = highlightedText(item.name, queryData.name)
+        return item
+      })
+      queryData.total = res.data.total
+    }
     loading.value = false
+  })
+}
+
+function handleFavorite(item: any) {
+  httpAjax(favoriteApi, {
+    favoriteFlag: item.favoriteFlag ? 0 : 1,
+    materialId: item.id,
+    type: "MUSIC"
+  }).then(res => {
     if (res.code === 200) {
-      listData.value = res.data.rows
-      paginationData.total = res.data.total
+      item.favoriteFlag = !item.favoriteFlag
     }
   })
 }
+function formatNumber(num: number) {
+  return num >= 10000 ? (num / 10000).toFixed(1).replace(/\.0$/, "") + "万" : num.toString()
+}
+const highlightedText = (text: string, query: string) => {
+  if (!text) {
+    return ""
+  }
+  if (!query) {
+    return text
+  }
+  const regex = new RegExp(`(${queryData.name})`, "gi")
+  return text.replace(regex, '<span class="highlighted">$1</span>')
+}
 </script>
 
 <style lang="scss" scoped>
 .cloudCoachList {
-  padding: 40px 40px 20px 40px;
   width: 100%;
   height: 100%;
-  .name {
-    height: 40px;
-    font-size: 20px;
+  .headCon {
+    width: 100%;
+    height: 64px;
+    border-bottom: 1px solid #eaeaea;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .headLeft {
+      margin-left: 30px;
+      display: flex;
+      align-items: center;
+      .tipImg {
+        width: 24px;
+        height: 24px;
+      }
+      .title {
+        font-weight: 600;
+        font-size: 18px;
+        color: #131415;
+        margin-left: 8px;
+      }
+    }
+    .headright {
+      margin-right: 30px;
+      display: flex;
+      align-items: center;
+      .closeBtn {
+        width: 24px;
+        height: 24px;
+        cursor: pointer;
+        &:hover {
+          opacity: 0.8;
+        }
+      }
+    }
   }
-  .listCon {
-    height: calc(100% - 100px);
-    overflow: auto;
-    .list {
+  .content {
+    width: 100%;
+    height: calc(100% - 64px);
+    .tabTools {
+      height: 72px;
+      width: 100%;
+      padding: 18px 30px;
       display: flex;
       justify-content: space-between;
       align-items: center;
-      height: 40px;
+      .tabCon {
+        display: flex;
+        .tab {
+          margin-right: 32px;
+          font-weight: 400;
+          font-size: 16px;
+          color: #8b8d98;
+          line-height: 22px;
+          cursor: pointer;
+          &:hover {
+            opacity: 0.8;
+          }
+          &:last-child {
+            margin-right: 0;
+          }
+          &.active {
+            font-weight: 600;
+            color: #131415;
+            position: relative;
+            &::after {
+              content: "";
+              position: absolute;
+              width: 100%;
+              height: 10px;
+              background: linear-gradient(90deg, #77bbff 0%, rgba(163, 231, 255, 0.22) 100%);
+              bottom: 0;
+              left: 0;
+              z-index: -1;
+            }
+          }
+        }
+      }
+      .query {
+        width: 400px;
+        height: 36px;
+        &::v-deep(.input) {
+          align-items: center;
+          padding: 0 3px 0 12px;
+          border-radius: 18px;
+          height: 100%;
+          &:not(.disabled):hover,
+          &.focused {
+            .img {
+              opacity: 1;
+            }
+            .queryBtn {
+              opacity: 1;
+            }
+          }
+          input {
+            font-size: 14px;
+          }
+          .img {
+            width: 16px;
+            height: 16px;
+            opacity: 0.4;
+          }
+          .queryBtn {
+            width: 60px;
+            height: 30px;
+            background: #198cfe;
+            border-radius: 16px;
+            font-weight: 500;
+            font-size: 14px;
+            color: #ffffff;
+            line-height: 30px;
+            text-align: center;
+            opacity: 0.4;
+            cursor: pointer;
+            &:hover {
+              opacity: 0.8 !important;
+            }
+          }
+        }
+      }
+    }
+    .musicListCon {
+      width: 100%;
+      height: calc(100% - 72px);
+      display: flex;
+      flex-direction: column;
+      .queryFrom {
+        &.isExpandAct {
+          height: 42px;
+          overflow: hidden;
+        }
+        flex-shrink: 0;
+        padding: 0 30px;
+        .queryFromList {
+          display: flex;
+          margin-bottom: 4px;
+          .tit {
+            flex-shrink: 0;
+            font-weight: 500;
+            font-size: 14px;
+            color: #131415;
+            line-height: 32px;
+            margin-right: 16px;
+          }
+          .queryFromCon {
+            display: flex;
+            flex-wrap: wrap;
+            .queryTip {
+              margin: 0 16px 12px 0;
+              font-weight: 400;
+              font-size: 14px;
+              color: rgba(0, 0, 0, 0.6);
+              line-height: 20px;
+              padding: 6px 16px;
+              background: #f5f6fa;
+              border-radius: 6px;
+              cursor: pointer;
+              display: flex;
+              align-items: center;
+              & > img {
+                width: 7px;
+                height: 4px;
+                margin-left: 6px;
+              }
+              &.active,
+              &:hover {
+                background: #d2ecff;
+                color: rgba(0, 0, 0, 1);
+                > img {
+                  transform: rotate(180deg);
+                }
+              }
+              &.hoverActive {
+                background: #d2ecff;
+                color: rgba(0, 0, 0, 1);
+              }
+            }
+          }
+        }
+      }
+      .isExpand {
+        flex-shrink: 0;
+        margin-bottom: 12px;
+        cursor: pointer;
+        display: flex;
+        justify-content: center;
+        font-weight: 400;
+        font-size: 14px;
+        color: #198cfe;
+        line-height: 20px;
+        align-items: center;
+        &:hover {
+          opacity: 0.8;
+        }
+        &.active > img {
+          transform: rotate(0deg);
+        }
+        & > img {
+          transform: rotate(180deg);
+          margin-left: 4px;
+          width: 10px;
+          height: 10px;
+        }
+      }
+      .musicListConBox {
+        flex-grow: 1;
+        overflow: hidden;
+        .musicList {
+          padding: 4px 0;
+          height: calc(100% - 60px);
+          overflow: auto;
+          &.empty {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+          }
+          .musicListBox {
+            width: calc(100% + 24px);
+            margin-left: -24px;
+            display: flex;
+            flex-wrap: wrap;
+            padding: 0 30px;
+            .musicCon {
+              margin-bottom: 24px;
+              width: calc(33.3333% - 24px);
+              margin-left: 24px;
+              padding: 16px;
+              background: #f5f6fa;
+              border-radius: 12px;
+              display: flex;
+              justify-content: space-between;
+              align-items: center;
+              height: 102px;
+              &:nth-last-child(-n + 3) {
+                margin-bottom: 0;
+              }
+              &:hover {
+                outline: 2px solid #198cfe;
+              }
+              .musicLeft {
+                display: flex;
+                align-items: center;
+                margin-right: 14px;
+                overflow: hidden;
+                .iconCon {
+                  position: relative;
+                  .icon {
+                    width: 70px;
+                    height: 70px;
+                    border-radius: 8px;
+                  }
+                  .jxImg {
+                    position: absolute;
+                    left: 0;
+                    top: 0;
+                    width: 34px;
+                    height: 16px;
+                  }
+                }
+                .musicInfo {
+                  margin-left: 12px;
+                  overflow: hidden;
+                  .musicTit {
+                    font-weight: 600;
+                    font-size: 15px;
+                    color: #131415;
+                    line-height: 21px;
+                    &::v-deep(.highlighted) {
+                      color: $themeColor;
+                    }
+                  }
+                  .info {
+                    margin-top: 13px;
+                    display: flex;
+                    align-items: center;
+                    .hotInfo {
+                      margin-right: 4px;
+                      padding: 0 4px;
+                      background: #fff3f3;
+                      border-radius: 3px;
+                      border: 1px solid rgba(254, 67, 67, 0.5);
+                      display: flex;
+                      align-items: center;
+                      justify-content: center;
+                      flex-shrink: 0;
+                      line-height: 16px;
+                      & > img {
+                        width: 10px;
+                        height: 12px;
+                      }
+                      & > div {
+                        margin-left: 2px;
+                        font-weight: 400;
+                        font-size: 12px;
+                        color: #fe4343;
+                      }
+                    }
+                    .play {
+                      margin-right: 4px;
+                      flex-shrink: 0;
+                      padding: 0 4px;
+                      background: #ffffff;
+                      border-radius: 3px;
+                      border: 1px solid rgba(243, 130, 26, 0.5);
+                      font-weight: 400;
+                      font-size: 12px;
+                      color: #f3821a;
+                      line-height: 16px;
+                      text-align: center;
+                    }
+                    .sing {
+                      margin-right: 4px;
+                      flex-shrink: 0;
+                      padding: 0 4px;
+                      background: #ffffff;
+                      border-radius: 3px;
+                      border: 1px solid rgba(21, 178, 253, 0.5);
+                      font-weight: 400;
+                      font-size: 12px;
+                      color: #00adff;
+                      line-height: 16px;
+                      text-align: center;
+                    }
+                    .musicUserName {
+                      overflow: hidden;
+                      font-weight: 400;
+                      font-size: 13px;
+                      color: #777777;
+                      line-height: 16px;
+                    }
+                  }
+                }
+              }
+              .musicRight {
+                flex-shrink: 0;
+                display: flex;
+                align-items: center;
+                .sc {
+                  width: 26px;
+                  height: 26px;
+                  cursor: pointer;
+                  &:hover {
+                    opacity: 0.8;
+                  }
+                }
+                .addBtn {
+                  margin-left: 12px;
+                  width: 54px;
+                  height: 26px;
+                  background: #198cfe;
+                  border-radius: 4px;
+                  font-weight: 600;
+                  font-size: 13px;
+                  color: #ffffff;
+                  line-height: 26px;
+                  text-align: center;
+                  cursor: pointer;
+                  &:hover {
+                    opacity: 0.8;
+                  }
+                }
+              }
+            }
+          }
+        }
+        .pagination {
+          padding: 0 30px;
+          display: flex;
+          justify-content: flex-end;
+          align-items: center;
+          height: 60px;
+        }
+      }
     }
-  }
-  .pagination {
-    margin-top: 20px;
-    display: flex;
-    justify-content: flex-end;
-    align-items: center;
-    height: 40px;
   }
 }
 </style>

+ 0 - 606
src/views/components/element/cloudCoachElement/cloudCoachList/cloudCoachList123.vue

@@ -1,606 +0,0 @@
-<template>
-  <div class="cloudCoachList">
-    <div class="headCon">
-      <div class="headLeft">
-        <img class="tipImg" src="@/views/Editor/CanvasTool/imgs/yp.png" alt="" />
-        <div class="title">乐谱</div>
-      </div>
-      <div class="headright">
-        <img @click="emits('close')" class="closeBtn" src="./imgs/close.png" alt="" />
-      </div>
-    </div>
-    <div class="content">
-      <div class="tabTools">
-        <div class="tabCon">
-          <div class="tab" :class="{ active: item.value === querData.tab }" v-for="item in tabData" :key="item.value">{{ item.label }}</div>
-        </div>
-        <div class="query">
-          <Input :placeholder="'请输入搜索关键词'" v-model:value="querData.value">
-            <template #prefix>
-              <img class="img" src="./imgs/query.png" alt="" />
-            </template>
-            <template #suffix>
-              <div class="queryBtn">搜索</div>
-            </template>
-          </Input>
-        </div>
-      </div>
-      <div class="musicListCon">
-        <div class="queryFrom">
-          <div class="queryFromList">
-            <div class="tit">标签:</div>
-            <div class="queryFromCon">
-              <div class="queryTip active">全部</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-            </div>
-          </div>
-          <div class="queryFromList">
-            <div class="tit">标签:</div>
-            <div class="queryFromCon">
-              <div class="queryTip">全部</div>
-              <div class="queryTip active">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <div class="queryTip">最新</div>
-              <Popover trigger="mouseenter" :offset="-4">
-                <template #content>
-                  <PopoverMenuItem>英式竖笛</PopoverMenuItem>
-                  <PopoverMenuItem>德式竖笛</PopoverMenuItem>
-                </template>
-                <div class="queryTip" :class="{ hoverActive: true }">
-                  <div>我是</div>
-                  <img src="./imgs/jt.png" alt="" />
-                </div>
-              </Popover>
-            </div>
-          </div>
-          <div class="isExpand" :class="{ active: isExpand }">
-            <div>{{ isExpand ? "收起" : "展开" }}</div>
-            <img src="./imgs/jiao.png" alt="" />
-          </div>
-        </div>
-        <!-- <div class="musicList empty"> -->
-        <div class="musicList">
-          <div class="musicListBox">
-            <div class="musicCon">
-              <div class="musicLeft">
-                <div class="iconCon">
-                  <img class="icon" src="" alt="" />
-                  <img class="jxImg" src="./imgs/jx.png" alt="" />
-                </div>
-                <div class="musicInfo">
-                  <EllipsisScroll class="musicTit" :title="'流浪地球流浪地球流浪地球流浪地球'" />
-                  <div class="info">
-                    <div class="hotInfo">
-                      <img src="./imgs/hot.png" alt="" />
-                      <div>1.8万</div>
-                    </div>
-                    <div class="play">演唱</div>
-                    <div class="sing">演奏</div>
-                    <div class="musicUserName">
-                      <EllipsisScroll :title="'耶寒尔'" />
-                    </div>
-                  </div>
-                </div>
-              </div>
-              <div class="musicRight">
-                <img class="sc" src="./imgs/sc.png" alt="" />
-                <div class="addBtn">添加</div>
-              </div>
-            </div>
-            <div class="musicCon">
-              <div class="musicLeft">
-                <div class="iconCon">
-                  <img class="icon" src="" alt="" />
-                  <img class="jxImg" src="./imgs/jx.png" alt="" />
-                </div>
-                <div class="musicInfo">
-                  <EllipsisScroll class="musicTit" :title="'流浪地球流浪地球流浪地球流浪地球'" />
-                  <div class="info">
-                    <div class="hotInfo">
-                      <img src="./imgs/hot.png" alt="" />
-                      <div>1.8万</div>
-                    </div>
-                    <div class="play">演唱</div>
-                    <div class="sing">演奏</div>
-                    <div class="musicUserName">
-                      <EllipsisScroll :title="'耶寒尔'" />
-                    </div>
-                  </div>
-                </div>
-              </div>
-              <div class="musicRight">
-                <img class="sc" src="./imgs/sc.png" alt="" />
-                <div class="addBtn">添加</div>
-              </div>
-            </div>
-            <div class="musicCon">
-              <div class="musicLeft">
-                <div class="iconCon">
-                  <img class="icon" src="" alt="" />
-                  <img class="jxImg" src="./imgs/jx.png" alt="" />
-                </div>
-                <div class="musicInfo">
-                  <EllipsisScroll class="musicTit" :title="'流浪地球流浪地球流浪地球流浪地球'" />
-                  <div class="info">
-                    <div class="hotInfo">
-                      <img src="./imgs/hot.png" alt="" />
-                      <div>1.8万</div>
-                    </div>
-                    <div class="play">演唱</div>
-                    <div class="sing">演奏</div>
-                    <div class="musicUserName">
-                      <EllipsisScroll :title="'耶寒尔'" />
-                    </div>
-                  </div>
-                </div>
-              </div>
-              <div class="musicRight">
-                <img class="sc" src="./imgs/sc.png" alt="" />
-                <div class="addBtn">添加</div>
-              </div>
-            </div>
-            <div class="musicCon">
-              <div class="musicLeft">
-                <div class="iconCon">
-                  <img class="icon" src="" alt="" />
-                  <img class="jxImg" src="./imgs/jx.png" alt="" />
-                </div>
-                <div class="musicInfo">
-                  <EllipsisScroll class="musicTit" :title="'流浪地球流浪地球流浪地球流浪地球'" />
-                  <div class="info">
-                    <div class="hotInfo">
-                      <img src="./imgs/hot.png" alt="" />
-                      <div>1.8万</div>
-                    </div>
-                    <div class="play">演唱</div>
-                    <div class="sing">演奏</div>
-                    <div class="musicUserName">
-                      <EllipsisScroll :title="'耶寒尔'" />
-                    </div>
-                  </div>
-                </div>
-              </div>
-              <div class="musicRight">
-                <img class="sc" src="./imgs/sc.png" alt="" />
-                <div class="addBtn">添加</div>
-              </div>
-            </div>
-          </div>
-          <!-- <Empty /> -->
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ElButton } from "element-plus"
-import Input from "@/components/Input.vue"
-import Popover from "@/components/Popover.vue"
-import PopoverMenuItem from "@/components/PopoverMenuItem.vue"
-import Empty from "@/components/Empty"
-import EllipsisScroll from "@/components/ellipsisScroll"
-import { reactive, ref } from "vue"
-
-const emits = defineEmits<{
-  (event: "update", id: string): void
-  (event: "close"): void
-}>()
-function handleUpdate() {
-  emits("update", "1760123974848413697")
-}
-
-const tabData = [
-  {
-    label: "相关资源",
-    value: "1"
-  },
-  {
-    label: "共享资源",
-    value: "2"
-  },
-  {
-    label: "我的资源",
-    value: "3"
-  },
-  {
-    label: "我的收藏",
-    value: "4"
-  }
-]
-
-const isExpand = ref(true)
-
-const querData = reactive({
-  tab: "1",
-  value: ""
-})
-</script>
-
-<style lang="scss" scoped>
-.cloudCoachList {
-  width: 100%;
-  height: 100%;
-  position: relative;
-  &::after {
-    width: 100%;
-    content: "";
-    position: absolute;
-    width: 100%;
-    height: 100px;
-    background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #ffffff 100%);
-    bottom: 0;
-    left: 0;
-  }
-  .headCon {
-    width: 100%;
-    height: 64px;
-    border-bottom: 1px solid #eaeaea;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    .headLeft {
-      margin-left: 30px;
-      display: flex;
-      align-items: center;
-      .tipImg {
-        width: 24px;
-        height: 24px;
-      }
-      .title {
-        font-weight: 600;
-        font-size: 18px;
-        color: #131415;
-        margin-left: 8px;
-      }
-    }
-    .headright {
-      margin-right: 30px;
-      display: flex;
-      align-items: center;
-      .closeBtn {
-        width: 24px;
-        height: 24px;
-        cursor: pointer;
-        &:hover {
-          opacity: 0.8;
-        }
-      }
-    }
-  }
-  .content {
-    width: 100%;
-    height: calc(100% - 64px);
-    .tabTools {
-      height: 72px;
-      width: 100%;
-      padding: 18px 30px;
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      .tabCon {
-        display: flex;
-        .tab {
-          margin-right: 32px;
-          font-weight: 400;
-          font-size: 16px;
-          color: #8b8d98;
-          line-height: 22px;
-          cursor: pointer;
-          &:hover {
-            opacity: 0.8;
-          }
-          &:last-child {
-            margin-right: 0;
-          }
-          &.active {
-            font-weight: 600;
-            color: #131415;
-            position: relative;
-            &::after {
-              content: "";
-              position: absolute;
-              width: 100%;
-              height: 10px;
-              background: linear-gradient(90deg, #77bbff 0%, rgba(163, 231, 255, 0.22) 100%);
-              bottom: 0;
-              left: 0;
-              z-index: -1;
-            }
-          }
-        }
-      }
-      .query {
-        width: 400px;
-        height: 36px;
-        &::v-deep(.input) {
-          align-items: center;
-          padding: 0 3px 0 12px;
-          border-radius: 18px;
-          height: 100%;
-          &:not(.disabled):hover,
-          &.focused {
-            .img {
-              opacity: 1;
-            }
-            .queryBtn {
-              opacity: 1;
-            }
-          }
-          input {
-            font-size: 14px;
-          }
-          .img {
-            width: 16px;
-            height: 16px;
-            opacity: 0.4;
-          }
-          .queryBtn {
-            width: 60px;
-            height: 30px;
-            background: #198cfe;
-            border-radius: 16px;
-            font-weight: 500;
-            font-size: 14px;
-            color: #ffffff;
-            line-height: 30px;
-            text-align: center;
-            opacity: 0.4;
-            cursor: pointer;
-            &:hover {
-              opacity: 0.8 !important;
-            }
-          }
-        }
-      }
-    }
-    .musicListCon {
-      width: 100%;
-      height: calc(100% - 72px);
-      overflow: auto;
-      overflow: overlay;
-      display: flex;
-      flex-direction: column;
-      .queryFrom {
-        padding: 0 30px;
-        .queryFromList {
-          display: flex;
-          margin-bottom: 4px;
-          .tit {
-            flex-shrink: 0;
-            font-weight: 500;
-            font-size: 14px;
-            color: #131415;
-            line-height: 32px;
-            margin-right: 16px;
-          }
-          .queryFromCon {
-            display: flex;
-            flex-wrap: wrap;
-            .queryTip {
-              margin: 0 16px 12px 0;
-              font-weight: 400;
-              font-size: 14px;
-              color: rgba(0, 0, 0, 0.6);
-              line-height: 20px;
-              padding: 6px 16px;
-              background: #f5f6fa;
-              border-radius: 6px;
-              cursor: pointer;
-              display: flex;
-              align-items: center;
-              & > img {
-                width: 7px;
-                height: 4px;
-                margin-left: 6px;
-              }
-              &.active,
-              &:hover {
-                background: #d2ecff;
-                color: rgba(0, 0, 0, 1);
-              }
-              &.hoverActive {
-                > img {
-                  transform: rotate(180deg);
-                }
-              }
-            }
-          }
-        }
-        .isExpand {
-          margin-bottom: 16px;
-          cursor: pointer;
-          display: flex;
-          justify-content: center;
-          font-weight: 400;
-          font-size: 14px;
-          color: #198cfe;
-          line-height: 20px;
-          align-items: center;
-          &:hover {
-            opacity: 0.8;
-          }
-          &.active > img {
-            transform: rotate(180deg);
-          }
-          & > img {
-            margin-left: 4px;
-            width: 10px;
-            height: 10px;
-          }
-        }
-      }
-      .musicList {
-        display: flex;
-        padding: 0 30px;
-        &.empty {
-          flex-grow: 1;
-          justify-content: center;
-          align-items: center;
-        }
-        .musicListBox {
-          width: calc(100% + 24px);
-          margin-left: -24px;
-          display: flex;
-          flex-wrap: wrap;
-          .musicCon {
-            margin-bottom: 24px;
-            width: calc(33.3333% - 24px);
-            margin-left: 24px;
-            padding: 16px;
-            background: #f5f6fa;
-            border-radius: 12px;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            &:hover {
-              outline: 2px solid #198cfe;
-            }
-            .musicLeft {
-              display: flex;
-              align-items: center;
-              margin-right: 14px;
-              overflow: hidden;
-              .iconCon {
-                position: relative;
-                .icon {
-                  width: 70px;
-                  height: 70px;
-                  border-radius: 8px;
-                }
-                .jxImg {
-                  position: absolute;
-                  left: 0;
-                  top: 0;
-                  width: 34px;
-                  height: 16px;
-                }
-              }
-              .musicInfo {
-                margin-left: 12px;
-                overflow: hidden;
-                .musicTit {
-                  font-weight: 600;
-                  font-size: 15px;
-                  color: #131415;
-                  line-height: 21px;
-                }
-                .info {
-                  margin-top: 13px;
-                  display: flex;
-                  align-items: center;
-                  .hotInfo {
-                    margin-right: 4px;
-                    padding: 0 4px;
-                    background: #fff3f3;
-                    border-radius: 3px;
-                    border: 1px solid rgba(254, 67, 67, 0.5);
-                    display: flex;
-                    align-items: center;
-                    justify-content: center;
-                    flex-shrink: 0;
-                    line-height: 16px;
-                    & > img {
-                      width: 10px;
-                      height: 12px;
-                    }
-                    & > div {
-                      font-weight: 400;
-                      font-size: 12px;
-                      color: #fe4343;
-                    }
-                  }
-                  .play {
-                    margin-right: 4px;
-                    flex-shrink: 0;
-                    padding: 0 4px;
-                    background: #ffffff;
-                    border-radius: 3px;
-                    border: 1px solid rgba(243, 130, 26, 0.5);
-                    font-weight: 400;
-                    font-size: 12px;
-                    color: #f3821a;
-                    line-height: 16px;
-                    text-align: center;
-                  }
-                  .sing {
-                    margin-right: 4px;
-                    flex-shrink: 0;
-                    padding: 0 4px;
-                    background: #ffffff;
-                    border-radius: 3px;
-                    border: 1px solid rgba(21, 178, 253, 0.5);
-                    font-weight: 400;
-                    font-size: 12px;
-                    color: #00adff;
-                    line-height: 16px;
-                    text-align: center;
-                  }
-                  .musicUserName {
-                    overflow: hidden;
-                    font-weight: 400;
-                    font-size: 13px;
-                    color: #777777;
-                    line-height: 16px;
-                  }
-                }
-              }
-            }
-            .musicRight {
-              flex-shrink: 0;
-              display: flex;
-              align-items: center;
-              .sc {
-                width: 26px;
-                height: 26px;
-                cursor: pointer;
-                &:hover {
-                  opacity: 0.8;
-                }
-              }
-              .addBtn {
-                margin-left: 12px;
-                width: 58px;
-                height: 26px;
-                background: #198cfe;
-                border-radius: 4px;
-                font-weight: 600;
-                font-size: 13px;
-                color: #ffffff;
-                line-height: 26px;
-                text-align: center;
-                cursor: pointer;
-                &:hover {
-                  opacity: 0.8;
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-}
-</style>

BIN
src/views/components/element/cloudCoachElement/cloudCoachList/imgs/jiao.png


BIN
src/views/components/element/cloudCoachElement/cloudCoachList/imgs/musicBg.png


+ 32 - 18
src/views/components/element/cloudCoachElement/cloudCoachPlayer/cloudCoachPlayer.vue

@@ -8,8 +8,12 @@
     }"
   >
     <div v-if="loading" class="loading-overlay">
-      <div class="spinner"></div>
-      <div class="text">云教练加载中...</div>
+      <div class="loadingBox">
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+      </div>
     </div>
     <iframe class="musicIframe" frameborder="0" :src="url" @load="handleIframeLoad"></iframe>
   </div>
@@ -19,6 +23,7 @@
 import { ref, computed } from "vue"
 import { getToken } from "@/libs/auth"
 import { YJL_URL_API } from "@/config/index"
+import queryParams from "@/queryParams"
 
 const props = withDefaults(
   defineProps<{
@@ -32,11 +37,11 @@ const props = withDefaults(
   }
 )
 const url = computed(() => {
-  return `${YJL_URL_API}?v=${Date.now()}&showGuide=true&showWebGuide=false&platform=pc&imagePos=right&zoom=0.8&modelType=practise&instrumentId=&iscurseplay=play&id=${props.id}&Authorization=${getToken()}`
+  return `${YJL_URL_API}?v=${Date.now()}&showGuide=true&showWebGuide=false&platform=pc&imagePos=right&zoom=0.8&modelType=practise&instrumentId=${queryParams.instrumentId}&iscurseplay=play&id=${props.id}&Authorization=${getToken()}`
 })
 
 // 先关闭这个功能
-const loading = ref(false)
+const loading = ref(true)
 function handleIframeLoad() {
   loading.value = false
 }
@@ -68,23 +73,32 @@ function handleIframeLoad() {
     flex-direction: column;
     z-index: 10;
     color: #fff;
-    background-color: #213793;
-    .spinner {
-      border: 4px solid #f3f3f3;
-      border-top: 4px solid #213793;
-      border-radius: 50%;
-      width: 40px;
-      height: 40px;
-      animation: spin 1s linear infinite;
-    }
-    .text {
-      margin-top: 10px;
+    background: url("../cloudCoachList/imgs/musicBg.png");
+    background-size: cover;
+    .loadingBox {
+      width: 30px;
+      height: 30px;
+      display: flex;
+      justify-content: space-between;
+      flex-wrap: wrap;
+      align-content: space-between;
+      animation: rotate 1.5s linear infinite;
+      .loadingItem {
+        width: 12px;
+        height: 12px;
+        border-radius: 50%;
+        background: #20bdff;
+        opacity: 0.5;
+        &:nth-child(2) {
+          opacity: 1;
+        }
+      }
     }
-    @keyframes spin {
-      0% {
+    @keyframes rotate {
+      from {
         transform: rotate(0deg);
       }
-      100% {
+      to {
         transform: rotate(360deg);
       }
     }

+ 1 - 1
src/viewsframe/errorPage/errorPage.vue

@@ -3,7 +3,7 @@
     <div class="error">
       <img src="./imgs/404.png" class="img" alt="" />
       <div class="tit">页面找不到了~</div>
-      <ElButton class="backBtn" type="primary" plain @click="handleGoHome">返回</ElButton>
+      <!-- <ElButton class="backBtn" type="primary" plain @click="handleGoHome">返回</ElButton> -->
     </div>
   </div>
 </template>