소스 검색

Merge branch 'hqyDevNewVersion' of http://git.dayaedu.com/liushengqiang/music-score into kt-dev

黄琪勇 1 년 전
부모
커밋
6814e10376

+ 2 - 1
src/helpers/customMusicScore.ts

@@ -536,10 +536,11 @@ export const resetFormate = () => {
 		// 给小节添加背景色
 		staves.forEach((stave: any) => {
 			const list = [
+				Array.from(stave?.querySelectorAll(".vf-StaveSection") || []),
+				Array.from(stave?.querySelectorAll(".vf-Volta") || []),
 				Array.from(stave?.querySelectorAll(".vf-clef") || []),
 				Array.from(stave?.querySelectorAll(".vf-keysignature") || []),
 				Array.from(stave?.getElementsByTagName("text") || []),
-				Array.from(stave?.querySelectorAll(".vf-StaveSection") || []),
 			].flat();
 			try {
 				if (list.length) {

+ 12 - 5
src/helpers/formateMusic.ts

@@ -1,7 +1,6 @@
 import dayjs from "dayjs";
 import duration from "dayjs/plugin/duration";
 import state, { customData } from "/src/state";
-import { metronomeData as metronomeDataState } from "./metronome"
 import { browser } from "../utils/index";
 import {
 	isSpecialMark,
@@ -774,6 +773,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	let multipleRestMeasures = 0;
 	let staveNoteIndex = 0;
 	let staveIndex = 0;
+	let xmlNoteTime = 0  // xml上面的音符时间
+	let xmlMp3BeatFixTime = 0 // xml上节拍器的时间
 
 	let preNoteEndTime = 0; // 上一个音符的结束时间
 
@@ -983,8 +984,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 					state.fixtime = fixtime;
 				}
 				// 存储mp3节拍器时间
-				metronomeDataState.xmlMp3BeatFixTime = getFixTime(beatSpeed)
-				// 
+				xmlMp3BeatFixTime = getFixTime(beatSpeed)
 				// console.log("fixtime:", fixtime, '速度:', beatSpeed, "state.isSpecialBookCategory:", state.isSpecialBookCategory, 'state.isOpenMetronome:', state.isOpenMetronome);
 			}
 			// console.log(getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator))
@@ -1210,7 +1210,14 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				measureSpeed,  // 小节速度
 				maxNoteNum: note.maxNoteNum, // 当前小节音符最多的分轨的音符数量
 				repeatIdx: iterator.repeatIdx || 0, // 标记是第几遍循环,从0开始
+				xmlNoteTime: retain(xmlNoteTime), // xml上音符开始时间 唱名用
+				xmlNoteEndTime: retain(xmlNoteTime + noteLength), //xml上音符结束时间 唱名用
+				xmlMp3BeatFixTime,  //xml上节拍器的时间
+				notBeatFixtime: state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime, // 不含节拍器的fixtime值 唱名用
+				notBeatTime: state.isEvxml && evNoteStartTime ? retain(evNoteStartTime) : retain(relativeTime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)), // 不含节拍器的 音符开始时间
+				notBeatEndTime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)) // 不含节拍器的 音符结束时间
 			};
+			xmlNoteTime += noteLength
 			// 如果是妙极客的曲子,并且第二遍循环播放需要等待时间,并且是第二遍循环的第一个小节的第一个音符
 			// if (state.isEvxml && state.secondEvXmlBeginTime && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
 			// 	nodeDetail.time = nodeDetail.time + state.secondEvXmlBeginTime;
@@ -1224,7 +1231,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				nodeDetail.endtime = nodeDetail.endtime + currentWaitTime;
 				usetime = usetime + currentWaitTime;
 				relativeTime = relativeTime + currentWaitTime;
-			}			
+			}		
 			nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail);
 			nodeDetail.duration = nodeDetail.endtime - nodeDetail.time;
 			let tickables = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables || [];
@@ -1256,7 +1263,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 		i++;
 	}
 	// 按照时间轴排序
-	const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => ({ ...item, i: index }));
+	const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => Object.assign(item,{i:index}));
 	// const sortArray = allNotes.sort((a, b) => a.time - b.time).map((item, index) => ({ ...item, i: index }));
 	// const sortArray = allNotes.map((item, index) => ({ ...item, i: index }));
 	console.timeEnd("音符跑完时间");

+ 0 - 1
src/helpers/metronome.ts

@@ -42,7 +42,6 @@ export const metronomeData = reactive({
 	cursorTips: '' as string, // 光标模式提示文字
 	followAudioIndex: 1, // 当前的拍数
 	totalNumerator: 2, // 总拍数
-	xmlMp3BeatFixTime: 0   // 当前xml mp3节拍器的时间 切换演奏和演唱计算时间用
 });
 
 watch(

BIN
src/page-instrument/header-top/image/sj.png


BIN
src/page-instrument/header-top/image/speed.png


+ 32 - 7
src/page-instrument/header-top/index.module.less

@@ -30,6 +30,21 @@
                 font-weight: 600;
                 font-size: 18px;
                 color: #FFFFFF;
+                .van-notice-bar__content{
+                    position: relative;
+                    padding-right: 16px;
+                    &::after{
+                        position: absolute;
+                        top: 50%;
+                        right: 0;
+                        transform: translateY(-50%);
+                        content: "";
+                        width: 11px;
+                        height: 6px;
+                        background: url("./image/sj.png") no-repeat;
+                        background-size: 100% 100%;                         
+                    }
+                }
             }
         }
     }
@@ -69,7 +84,7 @@
         flex-direction: column;
         align-items: center;
         cursor: pointer;
-        margin-right: 40px;
+        margin-right: 30px;
         &:last-child{
             margin-right: 0;
         }
@@ -88,23 +103,25 @@
     .metronomeBtn{
         position: relative;
         .speedCon{
-            padding: 1px 2px;
+            transform: scale(0.83);
+            transform-origin: left bottom;
+            padding: 2px;
             position: absolute;
             left: 14px;
-            top: -4px;
+            top: -9px;
             display: flex;
             align-items: center;
             background: #FFC121;
-            border-radius: 100px 100px 100px 1px;
+            border-radius: 120px 120px 120px 1px;
             border: 1px solid #FFFFFF;
             >img{
-                width: 12px;
-                height: 10px;
+                width: 15px;
+                height: 11px;
             }
             >div{
                 margin-left: 1px;
                 font-weight: 600;
-                font-size: 10px;
+                font-size: 12px;
                 color: #673207;
                 line-height: 1;
             }
@@ -216,6 +233,7 @@
         height: 38px;
         left: 27px;
         top: 17px;
+        cursor: pointer;
     }
     .name {
         position: absolute;
@@ -231,8 +249,15 @@
         display: flex;
         justify-content: space-between;
         padding: 0 36px;
+        &.twoModeBox{
+            justify-content: center;
+            > img + img{
+                margin-left: 150px;
+            }
+        }
         > img {
             width: calc((100% - 2*40px)/3);
+            max-width: 220px;
         }
     }
 }

+ 174 - 77
src/page-instrument/header-top/index.tsx

@@ -1,4 +1,4 @@
-import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef } from "vue";
+import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef,ComputedRef } from "vue";
 import styles from "./index.module.less";
 
 import iconBack from "./image/icon-back.png";
@@ -9,7 +9,7 @@ import { Badge, Circle, Popover, Popup, showConfirmDialog, showToast, NoticeBar
 import Speed from "./speed";
 import { evaluatingData, handleStartEvaluat } from "/src/view/evaluating";
 import Settting from "./settting";
-import state, { IPlatform, handleChangeSection, handleResetPlay, handleRessetState, togglePlay } from "/src/state";
+import state, { IPlatform, handleChangeSection, handleResetPlay, handleRessetState, togglePlay, IPlayState } from "/src/state";
 import { getAudioCurrentTime } from "/src/view/audio-list";
 import { followData, toggleFollow } from "/src/view/follow-practice";
 import { api_back } from "/src/helpers/communication";
@@ -89,6 +89,143 @@ export const headData = reactive({
   musicTypeShow: false,
 });
 
+let resetBtn:ComputedRef<{
+  display: boolean;
+  disabled: boolean;
+}>
+/**
+ * 处理模式切换
+ * @param oldPlayType   没改变之前的播放模式
+ * @param oldPlaySource  没改变之前的播放类型
+ * @param isforceReset   是否强制刷新播放状态 模式times时值改变时候也刷新
+ */
+export function handlerModeChange(oldPlayType:"play"|"sing", oldPlaySource:IPlayState,isforceReset?:boolean) {
+    const isModeChange = modeChangeHandleTimes(oldPlayType, oldPlaySource)
+    // 没有切换的时候 不处理下面的
+    if(isModeChange){
+      try {
+        metronomeData.metro.calculation(state.times);
+      } catch (error) {}
+      console.log("重新之后的times", state.times, state.fixtime)
+    }
+    if(isModeChange||isforceReset){
+      // 重置播放状态
+      handleRessetState()
+      // 隐藏重播按钮
+      resetBtn && (resetBtn.value.display = false)
+    }
+}
+// 模式切换之后重新给times赋值
+function modeChangeHandleTimes(oldPlayType:"play"|"sing", oldPlaySource:IPlayState){
+  const playType = state.playType
+  const playSource = state.playSource
+  const {notBeatFixtime, xmlMp3BeatFixTime} = state.times[0]
+  const { isOpenMetronome, isSingOpenMetronome } = state 
+  // 演奏向演唱切
+  if(oldPlayType === "play"&&playType === "sing"){
+    if(playSource === "mingSong"){
+      state.fixtime = 0
+      state.times.map(item => {
+        item.time = item.xmlNoteTime
+        item.endtime = item.xmlNoteEndTime
+        item.fixtime = 0
+      })
+      return true
+    }else{
+      //演奏开了节拍器,演唱没开节拍器
+      if(isOpenMetronome&&!isSingOpenMetronome){
+        state.fixtime = notBeatFixtime
+        state.times.map(item => {
+          item.time = item.notBeatTime
+          item.endtime = item.notBeatEndTime
+          item.fixtime = notBeatFixtime
+        })
+        return true
+      }else if(!isOpenMetronome&&isSingOpenMetronome){
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        state.times.map(item => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        })
+        return true
+      }
+    }
+  }else if(oldPlayType === "sing"&&playType === "play"){
+    // 演唱向演奏切
+    if(oldPlaySource === "mingSong"){
+      // 有节拍器
+      if(isOpenMetronome){
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        state.times.map(item => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        })
+        return true
+      }else{
+        state.fixtime = notBeatFixtime
+        state.times.map(item => {
+          item.time = item.notBeatTime
+          item.endtime = item.notBeatEndTime
+          item.fixtime = notBeatFixtime
+        })
+        return true
+      }
+    }
+    // 演奏开了节拍器,演唱没开节拍器
+    if(isOpenMetronome&&!isSingOpenMetronome){
+      state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+      state.times.map(item => {
+        item.time = item.notBeatTime + xmlMp3BeatFixTime
+        item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
+        item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+      })
+      return true
+    }else if(!isOpenMetronome&&isSingOpenMetronome){
+      state.fixtime = notBeatFixtime
+      state.times.map(item => {
+        item.time = item.notBeatTime
+        item.endtime = item.notBeatEndTime
+        item.fixtime = notBeatFixtime
+      })
+      return true
+    }
+  }else if(oldPlayType === "sing"&&playType === "sing"){
+    // 演唱之间切换  
+    // 切到唱名时候
+    if(playSource === "mingSong"){
+      state.fixtime = 0
+      state.times.map(item => {
+        item.time = item.xmlNoteTime
+        item.endtime = item.xmlNoteEndTime
+        item.fixtime = 0
+      })
+      return true
+    }else if(oldPlaySource === "mingSong"){
+      // 有节拍器
+      if(isSingOpenMetronome){
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        state.times.map(item => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        })
+        return true
+      }else{
+        state.fixtime = notBeatFixtime
+        state.times.map(item => {
+          item.time = item.notBeatTime
+          item.endtime = item.notBeatEndTime
+          item.fixtime = notBeatFixtime
+        })
+        return true
+      }
+    }
+  }
+  return false
+}
+
 export default defineComponent({
   name: "header-top",
   emits: ["close"],
@@ -200,6 +337,11 @@ export default defineComponent({
             // 原声, 伴奏 少一个,就不能切换
             if (state.music && state.accompany) return { display: true, disabled: false };
         } else {
+          // 播放过程中不能切换
+          if (state.playState === "play"){
+            return { display: true, disabled: true };
+          }
+          // 范唱 
           let index = 0
           state.fanSong && index++
           state.banSong && index++
@@ -223,15 +365,17 @@ export default defineComponent({
       // 音频播放中 禁用
       if (state.playState === "play") return { display: true, disabled: true };
       if (!state.isAppPlay) {
-        if(!state.isConcert){
           let index = 0
-          state.fanSong && index++
-          state.banSong && index++
-          state.mingSong && index++
-          if(index > 0) {
+          state.music && index++
+          state.accompany && index++
+          let songIndex = 0
+          state.fanSong && songIndex++
+          state.banSong && songIndex++
+          state.mingSong && songIndex++
+          // 演唱和演奏 都有数据的时间不禁用
+          if(songIndex>0&&index>0) {
             return { display: true, disabled: false };
           }
-        }
       }
       return {
         disabled: true,
@@ -266,7 +410,7 @@ export default defineComponent({
     });
 
     /** 重播按钮 */
-    const resetBtn = computed(() => {
+    resetBtn = computed(() => {
       // 选择模式 不显示
       if (headTopData.modeType !== "show") return { display: false, disabled: false };
       // 评测模式 不显示,跟练模式 不显示
@@ -428,61 +572,6 @@ export default defineComponent({
         console.log(e);
       }
     };
-
-    // 切换 演唱和演奏模式的时候处理
-    function handlerRefreshPlayType() {
-      // 妙极客的 曲子 时间是不变的,所以不做处理
-      if( !state.isEvxml ) {
-        // 重新计算state.times
-        const { isOpenMetronome, isSingOpenMetronome } = state
-        const { xmlMp3BeatFixTime } = metronomeData
-        if(state.playType === "play"){
-          if(isOpenMetronome && !isSingOpenMetronome){
-            state.fixtime = state.fixtime + xmlMp3BeatFixTime
-          } else if(!isOpenMetronome && isSingOpenMetronome){
-            state.fixtime = state.fixtime - xmlMp3BeatFixTime
-          }
-        }else{
-          if(isSingOpenMetronome && !isOpenMetronome){
-            state.fixtime = state.fixtime + xmlMp3BeatFixTime
-          } else if(!isSingOpenMetronome && isOpenMetronome){
-            state.fixtime = state.fixtime - xmlMp3BeatFixTime
-          }
-        }
-        const fixtime = state.fixtime
-        state.times.map(item => {
-          if(state.playType === "play"){
-            if(isOpenMetronome && !isSingOpenMetronome){
-              item.time = item.time + xmlMp3BeatFixTime
-              item.endtime = item.endtime + xmlMp3BeatFixTime
-              item.fixtime = fixtime
-            } else if(!isOpenMetronome && isSingOpenMetronome){
-              item.time = item.time - xmlMp3BeatFixTime
-              item.endtime = item.endtime - xmlMp3BeatFixTime
-              item.fixtime = fixtime
-            }
-          }else{
-            if(isSingOpenMetronome && !isOpenMetronome){
-              item.time = item.time + xmlMp3BeatFixTime
-              item.endtime = item.endtime + xmlMp3BeatFixTime
-              item.fixtime = fixtime
-            } else if(!isSingOpenMetronome && isOpenMetronome){
-              item.time = item.time - xmlMp3BeatFixTime
-              item.endtime = item.endtime - xmlMp3BeatFixTime
-              item.fixtime = fixtime
-            }
-          }
-        })
-        try {
-          metronomeData.metro.calculation(state.times);
-        } catch (error) {}
-        console.log("重新之后的times", state.times, fixtime)
-      }
-      // 重置播放状态
-      handleRessetState()
-      // 隐藏重播按钮
-      resetBtn.value.display = false
-    }
     return () => (
       <>
         <div
@@ -519,18 +608,21 @@ export default defineComponent({
             }
           </div>
           {/* 模式切换 */}
-          <div 
-            id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
-            style={{ display: toggleBtn.value.display ? "" : "none" }}
-            class={[styles.modeChangeBox, toggleBtn.value.disabled && styles.disabled]} 
-            onClick={() => {
-                handleRessetState();
-                headTopData.modeType = "init";
-            }}
-          >
-            <img class={styles.img} src={iconMode} />
-            <div class={styles.title}>{state.modeType==="practise" ? '练习模式' : state.modeType==="follow" ? "跟练模式" : state.modeType==="evaluating" ? "评测模式" : ""}</div>
-          </div>
+            { 
+            state.playType === "play" &&
+              <div 
+                id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
+                style={{ display: toggleBtn.value.display ? "" : "none" }}
+                class={[styles.modeChangeBox, toggleBtn.value.disabled && styles.disabled]} 
+                onClick={() => {
+                    handleRessetState();
+                    headTopData.modeType = "init";
+                }}
+              >
+                <img class={styles.img} src={iconMode} />
+                <div class={styles.title}>{state.modeType==="practise" ? '练习模式' : state.modeType==="follow" ? "跟练模式" : state.modeType==="evaluating" ? "评测模式" : ""}</div>
+              </div>
+          }
           {/* 功能按钮 */}
           <div
             class={[styles.headRight]}
@@ -569,6 +661,8 @@ export default defineComponent({
               style={{ display: playTypeBtn.value.display ? "" : "none" }}
               class={[styles.btn, playTypeBtn.value.disabled && styles.disabled]}
               onClick={() => {
+                const oldPlayType = state.playType
+                const oldPlaySource = state.playSource
                 if(state.playType === "play"){
                   state.playType = "sing"
                   state.playSource = state.fanSong?"music":state.banSong?"background":"mingSong"
@@ -576,7 +670,7 @@ export default defineComponent({
                   state.playType = "play"
                   state.playSource = state.music?"music":"background"
                 }
-                handlerRefreshPlayType()
+                handlerModeChange(oldPlayType, oldPlaySource, true)
               }}
             >
               <img style={{ display: state.playType === "play" ? "" : "none" }} class={styles.iconBtn} src={headImg(`perform.png`)} />
@@ -588,6 +682,8 @@ export default defineComponent({
               style={{ display: originBtn.value.display ? "" : "none" }}
               class={[styles.btn, originBtn.value.disabled && styles.disabled]}
               onClick={() => {
+                const oldPlayType = state.playType
+                const oldPlaySource = state.playSource
                 if(state.playType === 'play'){
                   state.playSource = state.playSource === "music" ? "background" : "music";
                 }else{
@@ -599,6 +695,7 @@ export default defineComponent({
                     state.playSource = state.fanSong ? "music" :"background"
                   }
                 }
+                handlerModeChange(oldPlayType, oldPlaySource)
               }}
             >
               <img style={{ display: state.playSource === "music" ? "" : "none" }} class={styles.iconBtn} src={state.playType === 'play'?headImg(`music.png`):headImg(`music1.png`)} />

+ 24 - 16
src/page-instrument/header-top/modeView.tsx

@@ -7,17 +7,11 @@ import glImg from "./image/gl.png"
 import pcImg from "./image/pc.png"
 import { headTopData } from "./index"
 import TheVip from "../custom-plugins/the-vip"
-import { getQuery } from "/src/utils/queryString";
-import { storeData } from "/src/store";
-import state from "/src/state";
-import { studentQueryUserInfo } from "../api";
-import { usePageVisibility } from "@vant/use";
-
-
-/* todo */
-/*
-   打击乐和节奏练习 模式可能不是3个 到时候根据字段来判断
- */
+import { getQuery } from "/src/utils/queryString"
+import { storeData } from "/src/store"
+import state from "/src/state"
+import { studentQueryUserInfo } from "../api"
+import { usePageVisibility } from "@vant/use"
 
 export default defineComponent({
    name: "modeView",
@@ -26,7 +20,7 @@ export default defineComponent({
       const data = reactive({
          showPC: false,
          showStudent: false,
-         showVip: false,
+         showVip: false
       })
       const openGuid = () => {
          // 加载后 判断 端口号 加载对应的引导
@@ -71,12 +65,26 @@ export default defineComponent({
       })
       return () => (
          <div class={[styles.modeView, headTopData.modeType !== "init" && styles.hidden]}>
-            <img src={backImg} class={styles.back} />
+            <img
+               src={backImg}
+               class={styles.back}
+               onClick={() => {
+                  headTopData.modeType = "show"
+               }}
+            />
             <img src={nameImg} class={styles.name} />
-            <div class={styles.modeBox}>
+            <div
+               class={[
+                  styles.modeBox,
+                  ((!state.isPercussion && !state.enableEvaluation) ||
+                     (state.isPercussion && state.enableEvaluation) ||
+                     (state.isPercussion && !state.enableEvaluation)) &&
+                     styles.twoModeBox
+               ]}
+            >
                <img src={lxImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("practise")} />
-               <img src={glImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("follow")} />
-               <img src={pcImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("evaluating")} />
+               {!state.isPercussion && <img src={glImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("follow")} />}
+               {state.enableEvaluation && <img src={pcImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("evaluating")} />}
             </div>
             {data.showVip && <TheVip />}
          </div>

+ 15 - 3
src/page-instrument/header-top/settting/index.tsx

@@ -3,9 +3,10 @@ import styles from "./index.module.less"
 import { headImg } from "../image";
 import { headTopData } from "../index"
 import { Switch, showToast, Field, Popup } from "vant";
-import state from "/src/state"
+import state, { refreshMusicSvg } from "/src/state"
 import { smoothAnimationState} from "/src/page-instrument/view-detail/smoothAnimation"
 import Recommendation from "../../custom-plugins/helper-model/recommendation";
+import { resetRenderMusicScore } from "/src/view/music-score";
 
 export default defineComponent({
 	name: "settting",
@@ -72,7 +73,13 @@ export default defineComponent({
                             <div class={styles.radioBox}>
                                 {
                                     [{name:'单行谱',value:true},{name:'多行谱',value:false}].map(item=>{
-                                        return <div class={ state.isSingleLine===item.value && styles.active } onClick={ ()=>{ state.isSingleLine = item.value } }>{item.name}</div>
+                                        return <div class={ state.isSingleLine===item.value && styles.active } onClick={ ()=>{ 
+                                            state.isSingleLine = item.value 
+                                            // resetRenderMusicScore(state.musicRenderType)
+                                            headTopData.settingMode = false
+                                            refreshMusicSvg();
+                                            // musicScoreRef.value?.refreshMusicScore()
+                                        } }>{item.name}</div>
                                     })
                                 }
                             </div>
@@ -82,7 +89,12 @@ export default defineComponent({
                             <div class={styles.radioBox}>
                                 {
                                     [{name:'五线谱',value:'staff'},{name:'首调',value:'firstTone'},{name:'固定谱',value:'fixedTone'}].map(item=>{
-                                        return <div class={ state.musicRenderType===item.value && styles.active } onClick={ ()=>{ state.musicRenderType = item.value as any} }>{item.name}</div>
+                                        return <div class={ state.musicRenderType===item.value && styles.active } onClick={ ()=>{ 
+                                            state.musicRenderType = item.value as any
+                                            // resetRenderMusicScore(state.musicRenderType)
+                                            headTopData.settingMode = false
+                                            refreshMusicSvg();
+                                        } }>{item.name}</div>
                                     })
                                 }
                             </div>

BIN
src/page-instrument/view-detail/emptyMusic/imgs/empty.png


+ 41 - 0
src/page-instrument/view-detail/emptyMusic/index.module.less

@@ -0,0 +1,41 @@
+.emptyMusic {
+    position: fixed;
+    z-index: 9999;
+    width: 100vw;
+    height: 100vh;
+    top: 0;
+    left: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: #fff;
+    .emptyMusicBox{
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        .img{
+            width: 202px;
+            height: 179px;
+        }
+        .tit{
+            margin-top: 6px;
+            font-weight: 400;
+            font-size: 16px;
+            color: #999999;
+            line-height: 22px;
+        }
+        .backBtn{
+            margin-top: 16px;
+            width: 100px;
+            height: 36px;
+            border-radius: 18px;
+            border: 1px solid #1CACF1;
+            font-weight: 400;
+            font-size: 16px;
+            color: #1CACF1;
+            line-height: 36px;
+            text-align: center;
+            cursor: pointer;
+        }
+    }
+}

+ 46 - 0
src/page-instrument/view-detail/emptyMusic/index.tsx

@@ -0,0 +1,46 @@
+import { defineComponent, ref } from "vue"
+import styles from "./index.module.less"
+import empty from "./imgs/empty.png"
+import { browser } from "/src/utils";
+import { HANDLE_WORK_ADD } from "../../custom-plugins/work-index";
+import { storeData } from "/src/store";
+import { api_back } from "/src/helpers/communication";
+import { getQuery } from "/src/utils/queryString";
+
+export const isEmptyMusicShow = ref(false)
+export default defineComponent({
+   name: "emptyMusic",
+   setup() {
+      const query = getQuery();
+      const browInfo = browser()
+      /** 返回 */
+      const handleBack = () => {
+         HANDLE_WORK_ADD()
+         // 不在APP中,
+         if (!storeData.isApp) {
+            window.close()
+            return
+         }
+         if ((browInfo.iPhone || browInfo.ios) && query.workRecord) {
+            setTimeout(() => {
+               api_back()
+            }, 550)
+            return
+         }
+         api_back()
+      }
+      return () => (
+         <>
+            {isEmptyMusicShow.value && (
+               <div class={styles.emptyMusic}>
+                  <div class={styles.emptyMusicBox}>
+                     <img class={styles.img} src={empty} />
+                     <div class={styles.tit}>曲目已失效</div>
+                     <div class={styles.backBtn} onClick={handleBack}>返回</div>
+                  </div>
+               </div>
+            )}
+         </>
+      )
+   }
+})

+ 2 - 1
src/page-instrument/view-detail/index.module.less

@@ -64,9 +64,10 @@
         margin-top: -13PX;
         border-radius: 4Px;
         background-color: rgba(25, 140, 254, 0.7);
-        opacity: var(--corsor-opacity);
+        // opacity: var(--corsor-opacity);
         //transform: translateX(10PX);
         z-index: 1 !important;
+        opacity: 0; // 新版小酷AI不显示光标指针
     }
 
     .staff {

+ 59 - 18
src/page-instrument/view-detail/index.tsx

@@ -1,5 +1,5 @@
 import { Popup, Skeleton } from "vant";
-import { computed, defineComponent, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, Transition, watch, watchEffect, defineAsyncComponent } from "vue";
+import { computed, defineComponent, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, Transition, watch, watchEffect, defineAsyncComponent, ref } from "vue";
 import { formateTimes } from "../../helpers/formateMusic";
 import Metronome, { metronomeData } from "../../helpers/metronome";
 import state, { EnumMusicRenderType, evaluatCreateMusicPlayer, handleSetSpeed, IAudioState, IPlatform, isRhythmicExercises, resetPlaybackToStart, togglePlay, getMusicDetail, addNoteBBox } from "/src/state";
@@ -9,7 +9,7 @@ import MusicScore from "../../view/music-score";
 import TestCheck from "/src/view/music-score/testCheck";
 import { sysMusicScoreAccompanimentQueryPage } from "../api";
 import EvaluatModel from "../evaluat-model";
-import HeaderTop from "../header-top";
+import HeaderTop, {handlerModeChange} from "../header-top";
 import styles from "./index.module.less";
 import { api_cloudAccompanyMessage, api_cloudLoading, api_keepScreenLongLight, api_openCamera, api_openWebView, api_setEventTracking, api_setRequestedOrientation, api_setStatusBarVisibility, isSpecialShapedScreen } from "/src/helpers/communication";
 import { getQuery } from "/src/utils/queryString";
@@ -35,6 +35,7 @@ import TheAudio from "/src/components/the-audio"
 import tickWav from "/src/assets/tick.mp3";
 import AuthorName from "../component/authorName"
 import { initSmoothAnimation } from "./smoothAnimation"
+import EmptyMusic, { isEmptyMusicShow } from "./emptyMusic"
 
 const DelayCheck = defineAsyncComponent(() =>
   import('/src/page-instrument/evaluat-model/delay-check')
@@ -78,6 +79,8 @@ const setNoteHalfTone = (list: any[]) => {
   return list;
 };
 
+export const musicScoreRef = ref();
+
 export default defineComponent({
   name: "music-list",
   setup() {
@@ -142,7 +145,13 @@ export default defineComponent({
       // Promise.all([sysMusicScoreAccompanimentQueryPage(id)]).then((values) => {
       //   getMusicInfo(values[0]);
       // });
-      await getMusicDetail(id);
+      try { 
+        await getMusicDetail(id);
+      } catch (err) {
+        console.error(err)
+        isEmptyMusicShow.value = true
+        return
+      }
       detailData.isLoading = false;
       // 如果后台设置了不显示指法,关闭指法开关
       if (!state.isShowFingering) {
@@ -169,6 +178,9 @@ export default defineComponent({
       setCustomGradual();
 			setCustomNoteRealValue();
       state.times = formateTimes(osmd);
+      // state.times = resetFrequency(state.times);
+      state.times = setNoteHalfTone(state.times);
+      console.log("🚀 ~ state.times:", state.times, state.subjectId, state);
       // 一行谱
       if (state.isSingleLine) {
         // 音符添加位置信息bbox
@@ -176,9 +188,6 @@ export default defineComponent({
         // 一行谱创建 动画
         initSmoothAnimation();
       }
-      // state.times = resetFrequency(state.times);
-      state.times = setNoteHalfTone(state.times);
-      console.log("🚀 ~ state.times:", state.times, state.subjectId, state);
       // 初始化midi音频信息
       const songEndTime = state.times[state.times.length - 1 || 0]?.endtime || 0
       if (state.isAppPlay) {
@@ -194,18 +203,16 @@ export default defineComponent({
       // 需要向外面(iframe)派发计时器数据的时候触发
       if(query.isbeatTimes){
         const { isOpenMetronome, isSingOpenMetronome } = state
-        const { xmlMp3BeatFixTime } = metronomeData
+        const {xmlMp3BeatFixTime} = state.times[0] 
         const singBeatTime: number[][] = []
         const beatTime = metronomeData.metroMeasure.map(metroMeasure => {
           const singBeat:number[] = []
           const beatTimeItem = metroMeasure.map((item: any) => {
             let singTime = item.time
-            if(!state.isEvxml){
-              if(isSingOpenMetronome && !isOpenMetronome){
-                singTime += xmlMp3BeatFixTime
-              } else if(!isSingOpenMetronome && isOpenMetronome){
-                singTime -= xmlMp3BeatFixTime
-              }
+            if(isSingOpenMetronome && !isOpenMetronome){
+              singTime += xmlMp3BeatFixTime
+            } else if(!isSingOpenMetronome && isOpenMetronome){
+              singTime -= xmlMp3BeatFixTime
             }
             singBeat.push(singTime)
             return item.time
@@ -213,23 +220,54 @@ export default defineComponent({
           singBeatTime.push(singBeat)
           return beatTimeItem
         })
-        console.log("webApi_beatTimes",{beatTime,singBeatTime})
+        //改为唱名
+        state.fixtime = 0
+        state.times.map(item => {
+          item.time = item.xmlNoteTime
+          item.endtime = item.xmlNoteEndTime
+          item.fixtime = 0
+        })
+        metronomeData.metro.calculation(state.times)
+        const mingBeatTime:number[][] = metronomeData.metroMeasure.map(metroMeasure => {
+          const beatTimeItem = metroMeasure.map((item: any) => {
+            return item.time
+          })
+          return beatTimeItem
+        })
+        console.log("webApi_beatTimes",{beatTime,singBeatTime,mingBeatTime})
         window.parent.postMessage(
           {
             api: "webApi_beatTimes",
-            data: JSON.stringify({beatTime,singBeatTime})
+            data: JSON.stringify({beatTime,singBeatTime,mingBeatTime})
           },
           "*"
         );
+        throw new Error("webApi_beatTimes 完成");
+      }
+      // 根据当前文件有没有 设置当前的播放模式
+      if(!state.music){
+        if(state.accompany){
+          state.playSource = "background"
+        }else{
+          if(state.fanSong){
+            state.playType = "sing"
+            state.playSource = "music"
+          }else if(state.banSong){
+            state.playType = "sing"
+            state.playSource = "background"
+          }else if(state.mingSong){
+            state.playType = "sing"
+            state.playSource = "mingSong"
+          }
+          handlerModeChange("play", "music")
+        }
       }
-      
       /**
        * 2024.1.25
        * 设置节拍器,跟练需要播放系统节拍器,所以不需要判断needTick状态
        */
       // if (state.needTick) {
-        const beatLengthInMilliseconds = (60 / state.speed) * 1000;
-        handleInitTick(beatLengthInMilliseconds, osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Numerator || 4);
+        handleInitTick(osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Numerator || 4);
       // }
       // api_cloudLoading();
       state.playBtnDirection = query.imagePos === 'right' ? 'right' : 'left';
@@ -434,6 +472,8 @@ export default defineComponent({
             </div>
           )}
         </Transition>
+        {/* 曲目加载错误的缺省 */}
+        <EmptyMusic></EmptyMusic>
         {/** 功能按钮 */}
         {
           !state.isPreView && 
@@ -456,6 +496,7 @@ export default defineComponent({
           {/* 曲谱渲染 */}
           {!detailData.isLoading && 
             <MusicScore 
+              ref={musicScoreRef}
               musicColor={'#FFFFFF'}
               showPartNames={state.isCombineRender}
               onRendered={handleRendered} 

+ 0 - 3
src/page-instrument/view-detail/smoothAnimation/index.less

@@ -25,9 +25,6 @@
     #cursorImg-0 {
         display: none;
     }
-    // .staveBox {
-    //     display: none !important;
-    // }
     .authorName{
         position: fixed;
         left: 0;

+ 48 - 6
src/page-instrument/view-detail/smoothAnimation/index.ts

@@ -17,6 +17,8 @@ type smoothAnimationType = {
    smoothBotDom: null | HTMLElement
    osmdCanvasPageDom: null | HTMLElement
    osdmScrollDom: null | HTMLElement
+   osdmScrollDomWith: number
+   selectionBoxDom: null | HTMLElement
    pointsPos: pointsPosType
    translateXNum: number
    aveSpeed: number
@@ -35,6 +37,8 @@ export const smoothAnimationState = {
    smoothBotDom: null,
    osmdCanvasPageDom: null,
    osdmScrollDom: null,
+   osdmScrollDomWith: 0,
+   selectionBoxDom: null,
    pointsPos: [], // 计算之后的点坐标数组
    translateXNum: 0, // 当前谱面的translateX的距离   谱面的位置信息 由translateX和scrollLeft的偏移一起决定
    aveSpeed: 0, // 谱面的一帧的平均速度
@@ -45,7 +49,6 @@ export const smoothAnimationState = {
 watch(smoothAnimationState.isShow, () => {
    if (smoothAnimationState.isShow.value) {
       smoothAnimationState.smoothAnimationBoxDom?.classList.remove("smoothAnimationBoxHide")
-      moveSmoothAnimation(moveState.progress, moveState.activeIndex)
    } else {
       smoothAnimationState.smoothAnimationBoxDom?.classList.add("smoothAnimationBoxHide")
    }
@@ -82,6 +85,7 @@ export function initSmoothAnimation() {
  * 销毁
  */
 export function destroySmoothAnimation() {
+   smoothAnimationState.isShow.value = false
    document.removeEventListener("resize", calcClientWidth)
    smoothAnimationState.smoothAnimationBoxDom?.remove()
    Object.assign(smoothAnimationState, {
@@ -98,6 +102,11 @@ export function destroySmoothAnimation() {
       aveSpeed: 0,
       clientWidth: 0
    })
+   Object.assign(moveState, {
+      oldIndex: -1,
+      progress: 0,
+      activeIndex: 0
+   })
 }
 
 /**
@@ -106,7 +115,7 @@ export function destroySmoothAnimation() {
 export function moveSmoothAnimationByPlayTime() {
    const currentTime = getAudioCurrentTime()
    if (currentTime <= state.fixtime) return
-   if (currentTime > state.times.last()?.time) return
+   if (currentTime > state.times.last()?.endtime) return
    // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
    let nextIndex = state.activeNoteIndex + 1
    let nextBBox = state.times[nextIndex]?.bbox
@@ -114,8 +123,10 @@ export function moveSmoothAnimationByPlayTime() {
       nextIndex += 1
       nextBBox = state.times[nextIndex]?.bbox
    }
-   // 当前的音符和下一个音符之间的时值
-   const noteDuration = state.times[nextIndex].time - state.times[state.activeNoteIndex]?.time
+   // 当前的音符和下一个音符之间的时值 (当是最后一个音符的时候,下一个音符的时间取当前音符的endtime)
+   const noteDuration =
+      (nextIndex > state.times.length - 1 ? state.times[state.activeNoteIndex]?.endtime : state.times[nextIndex].time) -
+      state.times[state.activeNoteIndex]?.time
    // 当前时值在该区间的占比
    const playProgress = (currentTime - state.times[state.activeNoteIndex]?.time) / noteDuration
    moveSmoothAnimation(playProgress, state.activeNoteIndex)
@@ -159,6 +170,13 @@ export function moveSmoothAnimation(progress: number, activeIndex: number) {
       smoothAnimationState.pointsPos,
       smoothAnimationState.pointsPos.slice(0, nowIndex)
    )
+   // 当移动到屏幕最右边时候 就不进行移动了
+   if (
+      (smoothAnimationState.osdmScrollDom?.scrollLeft || 0) + smoothAnimationState.translateXNum + smoothAnimationState.osdmScrollDomWith >
+      smoothAnimationState.canvasDomWith
+   ) {
+      return
+   }
    move_osmd(nowPointsPos)
 }
 
@@ -213,7 +231,16 @@ function move_osmd(nowPointsPos: pointsPosType[0]) {
    } else if (midBotNum > clientMidWidth + clientWidth * 0.45 && midBotNum <= clientMidWidth + clientWidth * 0.5) {
       smoothAnimationState.translateXNum += speed * 5
    }
-   smoothAnimationState.osmdCanvasPageDom!.style.transform = `translateX(-${smoothAnimationState.translateXNum}px)`
+   moveTranslateXNum(smoothAnimationState.translateXNum)
+}
+
+/**
+ * 根据 translateXNum 滚动到位置
+ */
+
+export function moveTranslateXNum(translateXNum: number) {
+   smoothAnimationState.osmdCanvasPageDom && (smoothAnimationState.osmdCanvasPageDom.style.transform = `translateX(-${translateXNum}px)`)
+   smoothAnimationState.selectionBoxDom && (smoothAnimationState.selectionBoxDom.style.transform = `translateX(-${translateXNum}px)`)
 }
 
 /**
@@ -236,9 +263,15 @@ function createSmoothAnimation() {
    // osdmScrollDom
    const osdmScrollDom = document.querySelector("#musicAndSelection") as HTMLElement
    smoothAnimationState.osdmScrollDom = osdmScrollDom
+   smoothAnimationState.osdmScrollDomWith = osdmScrollDom?.offsetWidth | 0
    // osmdCanvasPage
    const osmdCanvasPageDom = document.querySelector("#osmdCanvasPage1") as HTMLElement
    smoothAnimationState.osmdCanvasPageDom = osmdCanvasPageDom
+   // selectionBox
+   setTimeout(() => {
+      const selectionBoxDom = document.querySelector("#selectionBox") as HTMLElement
+      smoothAnimationState.selectionBoxDom = selectionBoxDom
+   }, 0)
    // box
    const smoothAnimationBoxDom = document.createElement("div")
    smoothAnimationBoxDom.className = "smoothAnimationBox"
@@ -286,13 +319,22 @@ function getPointsPosByBatePos(): pointsPosType {
          posArr.push({
             MeasureNumberXML: item.MeasureNumberXML,
             x: item.bbox.x,
+            // 当为休止符的时候 取最下面的位置*0.9,确保能显示完整
             y:
-               ((((item.frequency === -1 ? totalAv : item.frequency) - totalAv) / totalAv) * smoothAnimationState.canvasDomHeight) / 2 +
+               ((((item.frequency === -1 ? 2 * totalAv * 0.9 : item.frequency) - totalAv) / totalAv) * smoothAnimationState.canvasDomHeight) / 2 +
                smoothAnimationState.canvasDomHeight / 2 // cavans 高度为160 所以基准为80
          })
       }
       return posArr
    }, [])
+   // 最后一个音符延长(这里建立一个虚拟的音符延长)
+   const extendPoint = {
+      ...pointsPos[pointsPos.length - 1]
+   }
+   extendPoint.MeasureNumberXML++
+   // 当总长度减30小于最后一个音符时候,取最后一个音符加15
+   extendPoint.x = smoothAnimationState.canvasDomWith - 30 > extendPoint.x ? smoothAnimationState.canvasDomWith - 30 : extendPoint.x + 15
+   pointsPos.push(extendPoint)
    return pointsPos
 }
 

+ 146 - 16
src/state.ts

@@ -15,9 +15,10 @@ import { getMusicSheetDetail } from "./utils/baseApi"
 import { getQuery } from "/src/utils/queryString";
 import { followData } from "/src/view/follow-practice/index"
 import { changeSongSourceByBate } from "/src/view/audio-list"
-import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTime} from "/src/page-instrument/view-detail/smoothAnimation"
+import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTime, moveTranslateXNum, destroySmoothAnimation} from "/src/page-instrument/view-detail/smoothAnimation"
 import { storeData } from "/src/store";
 import { downloadXmlStr } from "./view/music-score"
+import { musicScoreRef } from "/src/page-instrument/view-detail/index"
 
 const query: any = getQuery();
 
@@ -499,6 +500,8 @@ const state = reactive({
   musicComposer: '',
   /** 作词家 */
   musicLyricist: '',
+  /** 加载中的文案 */
+  loadingText: '音频资源加载中,请稍后…',
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -895,6 +898,7 @@ export const gotoNext = (note: any, skipNote?: boolean) => {
       console.log(error);
     }
     // 重置 或者切换演奏演唱的时候 可能出现 state.activeNoteIndex === note.i的情况 执行
+    fillWordColor();
     if (state.isSingleLine && state.playState === "paused") {
       moveSvgDom(skipNote);
     }
@@ -928,6 +932,7 @@ export const gotoNext = (note: any, skipNote?: boolean) => {
   } catch (error) {
     console.log(error);
   }
+  fillWordColor();
   // 一行谱,需要滚动小节
   if (state.isSingleLine) {
     moveSvgDom(skipNote);
@@ -1126,7 +1131,7 @@ export const hanldeDirectSelection = (list: any[]) => {
   setTimeout(() => {
     state.section = formateSelectMearure(list);
     console.log('选段小节', state.section)
-  }, 500);
+  }, 0);
 };
 let offsetTop = 0;
 /**
@@ -1204,7 +1209,6 @@ const getMusicInfo = async (res: any) => {
   index = tracks.findIndex(item => {  // 筛选出当前的index
     return item === musicObj?.track
   })
-  index = index >= 0 ? index : 0;
   const musicInfo = {
     ...res.data,
     track: musicObj?.track
@@ -1237,6 +1241,10 @@ function initMusicSource (data: any, track?:string) {
     musicObj = musicSheetSoundList.find((item:any) => {
       return (isAllSubject||!instrumentId)?item.audioPlayType === "PLAY":(item.audioPlayType === "PLAY"&&item.musicalInstrumentId ==instrumentId)
     })
+    // 因为可能根据学生的乐器id也找不到曲目所以尝试取第一个
+    musicObj || (musicObj = musicSheetSoundList.find((item:any) => {
+      return item.audioPlayType === "PLAY"
+    }))
     fanSongObj = musicSheetSoundList.find((item:any) => {
       return item.audioPlayType === "SING"
     })
@@ -1252,6 +1260,10 @@ function initMusicSource (data: any, track?:string) {
   accompanyObj = musicSheetAccompanimentList.find((item:any) => {
     return item.audioPlayType === "PLAY"
   })
+  // 当没有任何曲目的时候报错
+  if(!musicObj?.audioFileUrl&&!accompanyObj?.audioFileUrl&&!fanSongObj?.audioFileUrl&&!banSongObj?.audioFileUrl&&!fanSongObj?.solmizationFileUrl){
+    throw new Error("该曲目无任何音源");
+  }
   Object.assign(state, {
     music: musicObj?.audioFileUrl,
     accompany: accompanyObj?.audioFileUrl,
@@ -1372,11 +1384,6 @@ const setState = (data: any, index: number) => {
     state.setting.displayFingering = false
   }
 
-  // 检测是否原音和伴奏都有
-  if (!state.music || !state.accompany) {
-    state.playSource = state.music ? "music" : "background";
-  }
-
   // 如果是PC端,放大曲谱
   state.platform = query.platform?.toLocaleUpperCase() || "";
   if (state.platform === IPlatform.PC) {
@@ -1519,8 +1526,8 @@ export const addNoteBBox = (list: any[]) => {
 
 }
 
-/** 跳动svgdom */
-export const moveSvgDom = (skipNote?: boolean) => {
+// 给歌词和音符添加动态颜色
+const fillWordColor = () => {
   // console.log('当前音符',state.activeNoteIndex)
   state.times.forEach((item: any, idx: number) => {
     const svgEl = document.getElementById(`vf-${state.times[idx]?.svgElement?.attrs?.id}`)
@@ -1545,8 +1552,11 @@ export const moveSvgDom = (skipNote?: boolean) => {
     if (index === currentNote.repeatIdx) {
       lyric?.classList.add('lyricActive')
     }
-  })
+  })  
+}
 
+/** 跳动svgdom */
+export const moveSvgDom = (skipNote?: boolean) => {
   /**
    * 计算需要移动的距离
    * 当前选中的音符和第一个音符之间的间距
@@ -1563,18 +1573,138 @@ export const moveSvgDom = (skipNote?: boolean) => {
   }
 }
 
+
+// 暂停的时候  恢复一行谱偏移位置信息
+watch(
+  () => state.playState,
+  () => {
+    if(state.isSingleLine){
+      // 当在播放中暂停 执行这个方法
+      if(!state.playEnd && state.playState === "paused") {
+        moveTranslateXNum(0)
+        const scrollLeft = smoothAnimationState.osdmScrollDom!.scrollLeft
+        smoothAnimationState.osdmScrollDom!.scrollLeft = scrollLeft + smoothAnimationState.translateXNum
+        smoothAnimationState.translateXNum = 0
+      }
+    }
+  }
+)
+
+
+/* 根据当前的小节获取前面有多少需要去除的合并小节 */
+function getNeedReduceMultipleRestNum(currMeasureIndex:number){
+  let needReduceMultipleRestNum = 0;
+  for(let noteIndex = 0; noteIndex < state.times.length; noteIndex++){
+    const note =  state.times[noteIndex];
+    if (note.MeasureNumberXML > currMeasureIndex) {
+      break;
+    }
+    if (note.multipleRestMeasures && note.multipleRestMeasures > 1) {
+      needReduceMultipleRestNum += 1;
+    }
+  }
+  return needReduceMultipleRestNum
+}
+
 watch(
 	() => state.activeMeasureIndex,
 	() => {
-    console.log('当前小节',state.activeMeasureIndex)
+    // 需要减去的合并小节数
+    const needReduceMultipleRestNum = getNeedReduceMultipleRestNum(state.activeMeasureIndex)
+    const matchMeasureNum = state.activeMeasureIndex - needReduceMultipleRestNum - 1
+    console.log('选中的小节',matchMeasureNum,'需要减去的小节',needReduceMultipleRestNum,'当前的小节',state.activeMeasureIndex)
     state.vfmeasures.forEach((item: any, idx: number) => {
-      if (idx === (state.activeMeasureIndex-1)) {
+      if (idx === matchMeasureNum) {
         item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#132D4C")
         item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#040D1E")
       } else {
-        item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#609FCF")
-        item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#2B70A5")
+        // 有选段只清除选段处的
+        if(state.section.length === 2){
+          // 把中间置灰
+          let leftMeasureNumberXML = state.section[0].MeasureNumberXML
+          let rightMeasureNumberXML = state.section[1].MeasureNumberXML
+          if(leftMeasureNumberXML > rightMeasureNumberXML){
+            leftMeasureNumberXML = state.section[1].MeasureNumberXML
+            rightMeasureNumberXML = state.section[0].MeasureNumberXML
+          }
+          const leftVfmeasuresIndex = leftMeasureNumberXML - getNeedReduceMultipleRestNum(leftMeasureNumberXML) - 1
+          const rightVfmeasuresIndex = rightMeasureNumberXML - getNeedReduceMultipleRestNum(rightMeasureNumberXML) - 1
+          if(idx >= leftVfmeasuresIndex && idx <= rightVfmeasuresIndex){
+            item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#609FCF")
+            item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#2B70A5")
+          }
+          // 预备小节
+          if(state.sectionFirst){
+            const sectionFirstVfmeasuresIndex = state.sectionFirst.MeasureNumberXML - getNeedReduceMultipleRestNum(state.sectionFirst.MeasureNumberXML) - 1
+            const sectionFirstDom = state.vfmeasures[sectionFirstVfmeasuresIndex]
+            sectionFirstDom?.querySelector('.vf-custom-bg')?.setAttribute("fill", "#71B8BD")
+            sectionFirstDom?.querySelector('.vf-custom-bot')?.setAttribute("fill", "#448F9C")
+          }
+        }else{
+          item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#609FCF")
+          item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#2B70A5")
+        }
       }
     })
 	}
-);
+);    
+
+
+
+watch(
+  () => state.section,
+  () => {
+    if(state.section.length === 2){
+      // 把前面和 后面置灰
+      let leftMeasureNumberXML = state.section[0].MeasureNumberXML
+      let rightMeasureNumberXML = state.section[1].MeasureNumberXML
+      if(leftMeasureNumberXML > rightMeasureNumberXML){
+        leftMeasureNumberXML = state.section[1].MeasureNumberXML
+        rightMeasureNumberXML = state.section[0].MeasureNumberXML
+      }
+      const leftVfmeasuresIndex = leftMeasureNumberXML - getNeedReduceMultipleRestNum(leftMeasureNumberXML) - 1
+      const rightVfmeasuresIndex = rightMeasureNumberXML - getNeedReduceMultipleRestNum(rightMeasureNumberXML) - 1
+      state.vfmeasures.forEach((item: any, idx: number) => {
+        // 小于选中置灰
+        if (idx < leftVfmeasuresIndex) {
+          item.querySelector('.vf-custom-bg')?.setAttribute("fill", "rgba(96,159,207,0.5)")
+          item.querySelector('.vf-custom-bot')?.setAttribute("fill", "rgba(43,112,165,0.5)")
+        }
+        // 大于选中置灰
+        if(idx > rightVfmeasuresIndex){
+          item.querySelector('.vf-custom-bg')?.setAttribute("fill", "rgba(96,159,207,0.5)")
+          item.querySelector('.vf-custom-bot')?.setAttribute("fill", "rgba(43,112,165,0.5)")
+        }
+      })
+      // 预备小节
+      if(state.sectionFirst){
+        const sectionFirstVfmeasuresIndex = state.sectionFirst.MeasureNumberXML - getNeedReduceMultipleRestNum(state.sectionFirst.MeasureNumberXML) - 1
+        const sectionFirstDom = state.vfmeasures[sectionFirstVfmeasuresIndex]
+        sectionFirstDom?.querySelector('.vf-custom-bg')?.setAttribute("fill", "#71B8BD")
+        sectionFirstDom?.querySelector('.vf-custom-bot')?.setAttribute("fill", "#448F9C")
+      }
+    }else{
+      // 恢复选段前
+      const matchMeasureNum = state.activeMeasureIndex - getNeedReduceMultipleRestNum(state.activeMeasureIndex) - 1
+      state.vfmeasures.forEach((item: any, idx: number) => {
+        if (idx === matchMeasureNum) {
+          item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#132D4C")
+          item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#040D1E")
+        } else {
+          item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#609FCF")
+          item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#2B70A5")
+        }
+      })
+    }
+  }
+)
+
+/** 刷新谱面 */
+export const refreshMusicSvg = () => {
+  state.loadingText = '正在加载中,请稍等…'
+  if(state.isSingleLine){
+    // 销毁旋律线
+    destroySmoothAnimation()
+  }
+  musicScoreRef.value?.refreshMusicScore()
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
src/view/audio-list/img/refresh_anim.json


+ 21 - 0
src/view/audio-list/index.module.less

@@ -65,4 +65,25 @@
             background-size: 100% 100%;
         }
     }
+}
+
+.loadingPop {
+    position: fixed;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    width: 100vw;
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    z-index: 10000;
+    background: rgba(0, 0, 0, .6);
+    .loadingTip {
+        font-size: 14px;
+        color: #fff;
+        margin-top: 14px;
+    }
 }

+ 1 - 1
src/view/audio-list/index.tsx

@@ -363,7 +363,7 @@ export default defineComponent({
 		// console.log(state.playMode, state.midiUrl);
 		return () => (
 			<>
-				<Loading/>
+				<Loading tipText={state.loadingText} />
 				<div class={styles.audioList}>
 					{state.playMode === "MIDI" && state.speed != 0 && (
 						<iframe

+ 22 - 8
src/view/audio-list/loading.tsx

@@ -1,12 +1,21 @@
-import { defineComponent, ref } from "vue"
+import { defineComponent, ref, watch } from "vue"
 import icon_loading_img from "./img/icon_loading_img.png"
 import { Progress } from "vant"
 import styles from "./index.module.less"
 import state from "/src/state"
+import { Vue3Lottie } from "vue3-lottie";
+import animBg from "./img/refresh_anim.json";
 
 export default defineComponent({
    name: "loading",
-   setup() {
+   props: {
+		/** 提示文案 */
+		tipText: {
+			type: String,
+			default: '',
+		},
+	},
+   setup(props) {
       function fakeLoadingProgress(duration = 2000, callback: (num: number) => void) {
          let startTime = Date.now()
          let progress = 0
@@ -25,14 +34,19 @@ export default defineComponent({
       fakeLoadingProgress(2000, num => {
          loadingProress.value = num
       })
+      watch(
+         () => state.audioDone,
+         () => {
+           if (!state.audioDone) {
+            loadingProress.value = 0
+           }
+         }
+       );
       return () =>
          !state.audioDone && (
-            <div class={styles.loading}>
-               <div class={styles.loadingWrap}>
-                  <img class={styles.loadingIcon} src={icon_loading_img} />
-                  <Progress percentage={loadingProress.value} />
-                  <div class={styles.loadingTip}>音频资源加载中,请稍后</div>
-               </div>
+            <div class={styles.loadingPop}>
+               <Vue3Lottie animationData={animBg}></Vue3Lottie>
+               <div class={styles.loadingTip}>{props.tipText}</div>
             </div>
          )
    }

+ 35 - 3
src/view/music-score/index.tsx

@@ -9,6 +9,7 @@ import queryString from "query-string";
 import { getGradualLengthByXml } from "/src/helpers/calcSpeed";
 import { resetFormate, resetGivenFormate, setGlobalMusicSheet } from "/src/helpers/customMusicScore"
 import { setGlobalData } from "/src/utils";
+import Loading from "/src/view/audio-list/loading"
 
 export const musicRenderTypeKey = "musicRenderType";
 let osmd: any = null;
@@ -17,6 +18,7 @@ const musicData = reactive({
 	isRenderLoading: true,
 	score: "",
 	containerWidth: 0,
+	isRefreshLoading: false,
 });
 
 /** 重新渲染曲谱 */
@@ -25,7 +27,8 @@ export const resetRenderMusicScore = (type?: string) => {
 	const newSearch = queryString.stringify({
 		...search,
 		_t: Date.now(),
-		musicRenderType: type
+		musicRenderType: type,
+		isSingleLine: state.isSingleLine
 	});
 	location.search = "?" + newSearch;
 };
@@ -56,7 +59,7 @@ export default defineComponent({
 			default: false,
 		},
 	},
-	setup(props, { emit, slots }) {
+	setup(props, { emit, slots, expose }) {
 		/** 设置 曲谱模式,五线谱还是简谱 */
 		const setRenderType = () => {
 			const musicRenderType: any = sessionStorage.getItem(props.renderTypeKey || musicRenderTypeKey);
@@ -103,9 +106,10 @@ export default defineComponent({
 			// osmd.EngravingRules.PageRightMargin = state.isSingleLine ? (window.innerWidth+200)/10 : 2;
 			// osmd.EngravingRules.FixedMeasureWidth = state.isSingleLine ? true : false; // 是否固定小节的宽度(小节同一宽度渲染)
 			//osmd.EngravingRules.PageTopMargin = state.platform === IPlatform.PC ? 0 : 1; // 老师端顶部间距
-			osmd.EngravingRules.PageTopMargin = 0; // 老师端顶部间距
+			osmd.EngravingRules.PageTopMargin = 2; // 老师端顶部间距
 			osmd.EngravingRules.PageTopMarginNarrow = 3;
 			osmd.EngravingRules.PageLeftMargin = 2;
+			osmd.EngravingRules.PageRightMargin = 2;
 			osmd.EngravingRules.BreathMarkDistance = 0.1; // 呼吸标记距离音符的位置,百分比
 			// 老师端上课页面,左右两边有功能按钮,所以左右边距需要加大
 			// if (state.isAttendClass && state.platform === IPlatform.PC) {
@@ -160,6 +164,34 @@ export default defineComponent({
 			}
 			return result;
 		});
+
+		/** 刷新曲谱 */
+		const refreshMusicScore = async () => {
+			console.log('刷新谱面123')
+			const container = document.getElementById('musicAndSelection'), svgDom = document.getElementById('osmdCanvasPage1'), selectionBox = document.getElementById('selectionBox');
+			if (container && svgDom) {
+				container?.removeChild(svgDom)
+				container?.removeChild(selectionBox)
+			}
+			state.vfmeasures = [];
+			musicData.showSelection = false;
+			state.osmd.clear();
+			musicData.isRenderLoading = true;
+			musicData.isRefreshLoading = true;
+			state.audioDone = false;
+			getContainerWidth();
+			setRenderType();
+			await getXML();
+			await init();
+			musicData.isRenderLoading = false;
+			musicData.isRefreshLoading = false;
+			state.audioDone = true;
+			musicData.showSelection = true;
+		}
+		expose({
+			refreshMusicScore,
+		})
+
 		return () => (
 			<div
 				id="musicAndSelection"

BIN
src/view/selection/imgs/close.png


+ 26 - 65
src/view/selection/index.module.less

@@ -16,75 +16,36 @@
     // background: rgba(0,0,0,0.3);
 }
 
-
-.staveBox {
-    background-color: var(--active-stave-box) !important;
-}
-
-.leftStaveBox {
-    background-color: var(--active-stave-box);
-
-    &::before {
-        content: '';
-        position: absolute;
-        left: -5px;
-        top: -5px;
-        width: 5px;
-        height: 100%;
-        border-top: 5px solid var(--van-primary-color);
-        border-left: 5px solid var(--van-primary-color);
-        border-bottom: 5px solid var(--van-primary-color);
-    }
-}
-
-.rightStaveBox {
-    background-color: var(--active-stave-box);
-
-    &::after {
-        content: '';
-        position: absolute;
-        right: -5px;
-        top: -5px;
-        width: 5px;
-        height: 100%;
-        border-top: 5px solid var(--van-primary-color);
-        border-right: 5px solid var(--van-primary-color);
-        border-bottom: 5px solid var(--van-primary-color);
-    }
-}
-
-.centerStaveBox {
-    background-color: var(--active-stave-box);
-
-    &::before {
-        content: '';
-        position: absolute;
-        left: -5px;
-        top: -5px;
-        width: 5px;
-        height: 100%;
-        border-top: 5px solid var(--van-primary-color);
-        border-left: 5px solid var(--van-primary-color);
-        border-bottom: 5px solid var(--van-primary-color);
-    }
-
-    &::after {
-        content: '';
+.selectBox{
+    position: absolute;
+    width: 3Px;
+    background-color: #FFB800;
+    z-index: 9;
+    .selectHandle{
         position: absolute;
-        right: -5px;
-        top: -5px;
-        width: 5px;
-        height: 100%;
-        border-top: 5px solid var(--van-primary-color);
-        border-right: 5px solid var(--van-primary-color);
-        border-bottom: 5px solid var(--van-primary-color);
+        top: -22Px;
+        right: 0;
+        width: 22Px;
+        height: 22Px;
+        cursor: pointer;
+        background-color: #FFB800;
+        background-image: url("./imgs/close.png");
+        background-repeat: no-repeat;
+        background-size: 15Px 15Px;
+        background-position: 4Px 3Px;
+        border-radius: 100Px 0 0 100Px;
+        &.selectHandleRight{
+            right: -19Px;
+            background-position: 3Px 3Px;
+            border-radius: 0 100Px 100Px 0;
+        }
+        &.playIng{
+            pointer-events: none;
+            background-image:none; 
+        }
     }
 }
 
-.prepareStaveBox {
-    background-color: rgba(255, 98, 37, 0.18);
-}
-
 .disable {
     pointer-events: none;
 }

+ 59 - 46
src/view/selection/index.tsx

@@ -1,10 +1,10 @@
-import { computed, defineComponent, onMounted, reactive, Transition, nextTick } from "vue";
+import { computed, defineComponent, onMounted, reactive, Transition, nextTick, watch } from "vue";
 import state, { EnumMusicRenderType, handleSelection, skipNotePlay, IPlatform } from "/src/state";
 import styles from "./index.module.less";
 import { metronomeData } from "/src/helpers/metronome";
 import { evaluatingData } from "../evaluating";
 import { leveByScoreMeasureIcons } from "../evaluating/evaluatResult";
-import { Icon } from "vant";
+import { Icon, showToast } from "vant";
 import MoveMusicScore, { moveData, renderForMoveData } from "../plugins/move-music-score";
 import { useRoute } from "vue-router";
 import { getQuery } from "/src/utils/queryString";
@@ -173,53 +173,38 @@ export default defineComponent({
 		const disableClickNote = computed(() => {
 			return state.sectionStatus || state.modeType !== "practise";
 		});
-		const showClass = computed(() => {
-			return (item: any) => {
-				if (state.sectionStatus) {
-					if (state.section.length === 1) {
-						if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
-							return styles.leftStaveBox;
+		// 选段符号
+		const sectionPosData = computed(() => {
+			if(state.sectionStatus) {
+				return state.section.map(((item,index) => {
+					if(index === 0){
+						const currItem = selectData.staves.find(stave => {
+							return stave.MeasureNumberXML === item.MeasureNumberXML	
+						})
+						return currItem && {
+							left: currItem.staveBox.left,
+							top: currItem.staveBox.top,
+							height: selectData.measureHeight + 'px'  // 小节的高度
 						}
-					}
-					if (state.section.length === 2) {
+					} else {
 						// 实际的结束位置
-						const actualEndIndex = state.userChooseEndIndex > state.section[1].MeasureNumberXML ? state.userChooseEndIndex : state.section[1].MeasureNumberXML
-						// 选段预备拍背景
-						if (state.sectionFirst && item.MeasureNumberXML === state.sectionFirst.MeasureNumberXML) {
-							item.staveBox.height = selectData.measureHeight + 'px';
-							return styles.prepareStaveBox;
+						const actualEndIndex = state.userChooseEndIndex > item.MeasureNumberXML ? state.userChooseEndIndex : item.MeasureNumberXML
+						const currItem = selectData.staves.find(stave => {
+							return stave.MeasureNumberXML === actualEndIndex
+						})
+						return currItem && {
+							left: parseFloat(currItem.staveBox.left)+parseFloat(currItem.staveBox.width)+"px",
+							top: currItem.staveBox.top,
+							height: selectData.measureHeight + 'px'
 						}
-						if (
-							item.MeasureNumberXML >= state.section[0].MeasureNumberXML &&
-							item.MeasureNumberXML <= actualEndIndex
-						) {
-							if (
-								item.MeasureNumberXML == state.section[0].MeasureNumberXML &&
-								item.MeasureNumberXML == actualEndIndex
-							) {
-								return styles.centerStaveBox;
-							}
-							if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
-								return styles.leftStaveBox;
-							}
-							if (item.MeasureNumberXML == actualEndIndex) {
-								if (!item.staveBox?.height) {
-									item.staveBox.height = selectData.measureHeight + 'px'
-								}
-								return styles.rightStaveBox;
-							}
-							return styles.staveBox + " staveBox";  // 加上固定css 一行谱可以隐藏
-						}
-					}
-				} else {
-					if (state.activeMeasureIndex == item.MeasureNumberXML && !state.isReport) {
-						item.staveBox.height = selectData.measureHeight + 'px';
-						return styles.staveBox + " staveBox"; // 加上固定css 一行谱可以隐藏
 					}
-				}
-			};
-		});
+				}))
+			}
+			return []
+		})
 		onMounted(() => {
+			selectData.notes = [];
+			selectData.staves = [];
 			calcNoteData();
 			// 初始化谱面可移动的元素位置
 			try {
@@ -260,7 +245,6 @@ export default defineComponent({
 								<div
 									class={[
 										styles.position,
-										showClass.value(item),
 										scoreItem ? `scoreItemLeve${scoreItem.leve}` : "",
 										item.multipleRestMeasures <= 1 ? styles.staveBg : "",
 										(state.platform === IPlatform.PC && state.zoom > 0.8) ? styles.linePC : '',
@@ -320,7 +304,36 @@ export default defineComponent({
 						</div>
 					);
 				})}
-
+				{/* 选段 */}
+				{
+					sectionPosData.value.map((item,index) =>{
+						return (
+							item && <div class={styles.selectBox} style={item}>
+								<div class={[styles.selectHandle,index>0&&styles.selectHandleRight,state.playState==="play"&&styles.playIng]} onClick={()=>{
+									// 如果选择了2个 删除左边的时候
+									if(state.section.length===2&&index === 0){
+										state.section = []
+										showToast({
+											message: "请选择开始小节",
+											duration: 0,
+											position: "top",
+											className: "selectionToast",
+										});
+									}else{
+										state.section.splice(index,1)
+										state.section = [...state.section]  // 触发 watch
+										showToast({
+											message: state.section.length?"请选择结束小节":"请选择开始小节",
+											duration: 0,
+											position: "top",
+											className: "selectionToast",
+										});
+									}
+								}}></div>
+							</div>
+						)
+					})
+				}
 				{/* 移动模块 */}
 				{query.isMove == "1" && <MoveMusicScore />}
 			</div>

+ 26 - 31
src/view/tick/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, reactive, onMounted } from "vue";
+import { defineComponent, reactive, onMounted, computed } from "vue";
 import tockAndTick from "/src/constant/tockAndTick.json";
 import { Howl } from "howler";
 import { Popup } from "vant";
@@ -9,17 +9,24 @@ import tickWav from "/src/assets/tick.mp3";
 import tockWav from "/src/assets/tock.mp3";
 
 const tickData = reactive({
-	list: [] as number[],
 	len: 0,
+	reduceLen: 0,
 	tickEnd: false,
 	/** 节拍器时间 */
 	beatLengthInMilliseconds: 0,
-	state: "",
-	source1: "" as unknown as Howl,
-	source2: "" as unknown as Howl,
 	index: 0,
-	show: false,
+	show: false
 });
+
+// 是否使用系统节拍器
+const isUseSystemBeat = computed(()=>{
+	return (state.playType === "play"&& !state.isOpenMetronome)||(state.playType === "sing" && !state.isSingOpenMetronome)
+})
+// 使用哪个节拍器个数
+const useLen = computed(()=>{
+	return isUseSystemBeat.value ? tickData.reduceLen : tickData.len
+})
+
 let _time: NodeJS.Timeout 
 // 关闭节拍器
 export function closeTick(){
@@ -37,11 +44,11 @@ const handlePlay = (i: number, source: any | null) => {
 				return
 			};
 			tickData.index++;
-			if (source) {
+			// 当系统节拍器才播放声音
+			if (source && isUseSystemBeat.value) {
 				const beatVolume = state.setting.beatVolume / 100
 				source.volume = beatVolume;
-				// 当mp3节拍器的时候不播放声音
-				if (source.volume <= 0 || state.isOpenMetronome || state.isSingOpenMetronome) {
+				if (source.volume <= 0) {
 					source.muted = true
 				} else {
 					source.muted = false
@@ -73,13 +80,15 @@ const createAudio = (src: string): Promise<HTMLAudioElement | null> => {
 };
 
 /** 设置节拍器
- * @param beatLengthInMilliseconds 节拍间隔时间
  * @param beat 节拍数
  */
-export const handleInitTick = (beatLengthInMilliseconds: number, beat: number) => {
-	tickData.state = "";
-	tickData.beatLengthInMilliseconds = beatLengthInMilliseconds
+export const handleInitTick = (beat: number) => {
 	tickData.len = beat;
+	// 节拍器的个数除以2 直到小于等于4为止 
+	while (beat > 4 && beat % 2 === 0) {
+        beat = beat / 2;
+    }
+	tickData.reduceLen = beat
 };
 
 /** 开始节拍器 */
@@ -87,27 +96,13 @@ export const handleInitTick = (beatLengthInMilliseconds: number, beat: number) =
 export const handleStartTick = async () => {
 	tickData.show = true;
 	tickData.tickEnd = false;
-	if (tickData.state !== "ok") {
-		// tickData.source1 = new Howl({
-		// 	src: tockAndTick.tick,
-		// 	// 如果是ios手机,需要强制使用audio,不然部分系统版本第一次播放没有声音
-		// 	html5: browserInfo.ios,
-		// });
-
-		// tickData.source2 = new Howl({
-		// 	src: tockAndTick.tock,
-		// });
-		tickData.state = "ok";
-	}
 	tickData.index = 0;
 	tickData.beatLengthInMilliseconds = (60 / state.speed) * 1000;
-	for(let i = 0; i <= tickData.len; i++){
+	for(let i = 0; i <= useLen.value; i++){
 		// 提前结束, 直接放回false
 		if (tickData.tickEnd) return false;
-		// Howl 插件播放音频
-		// const source = i === 0 ? tickData.source1 : i === tickData.len ? null : tickData.source2;
 		// Audio 标签播放音频
-		const source = i === 0 ? audioData.tick : i === tickData.len ? null : audioData.tock;
+		const source = i === 0 ? audioData.tick : i === useLen.value ? null : audioData.tock;
 		await handlePlay(i, source)
 	}
 	tickData.show = false;
@@ -150,8 +145,8 @@ export default defineComponent({
 				tickData.show && 
 				<div class={styles.dots} style={posObj}>
 					{
-						Array.from({ length: tickData.len }).map((item,index)=>{
-							return <div class={[styles.dot,((tickData.len - tickData.index)<=index)&&styles.hide]}></div>
+						Array.from({ length: useLen.value }).map((item,index)=>{
+							return <div class={[styles.dot,((useLen.value - tickData.index)<=index)&&styles.hide]}></div>
 						})
 					}
 				</div>

+ 1 - 1
vite.config.ts

@@ -78,7 +78,7 @@ export default defineConfig({
         // target: "https://kt.colexiu.com",
         target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
         //target: "https://dev.resource.colexiu.com",
-        // target: "https://dev.kt.colexiu.com",
+        //target: "https://dev.kt.colexiu.com",
         //target: "https://mec.colexiu.com",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/instrument/, ""),

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.