浏览代码

Merge branch 'feature-tianyong-newVersion' into kt-dev

TIANYONG 1 年之前
父节点
当前提交
b372be0bbb
共有 47 个文件被更改,包括 733 次插入394 次删除
  1. 3 3
      instrument.html
  2. 2 2
      public/flexible.js
  3. 二进制
      src/assets/DIN_Alternate_Bold.ttf
  4. 45 6
      src/helpers/customMusicScore.ts
  5. 8 2
      src/helpers/formateMusic.ts
  6. 1 1
      src/page-instrument/component/authorName/index.tsx
  7. 1 0
      src/page-instrument/custom-plugins/helper-model/recommendation/index.module.less
  8. 8 0
      src/page-instrument/evaluat-model/earphone/index.module.less
  9. 5 1
      src/page-instrument/evaluat-model/earphone/index.tsx
  10. 13 3
      src/page-instrument/evaluat-model/evaluat-result/index.module.less
  11. 5 4
      src/page-instrument/evaluat-model/evaluat-result/index.tsx
  12. 8 0
      src/page-instrument/evaluat-model/index.module.less
  13. 19 6
      src/page-instrument/evaluat-model/index.tsx
  14. 12 11
      src/page-instrument/follow-model/microphone/index.module.less
  15. 4 2
      src/page-instrument/follow-model/microphone/index.tsx
  16. 21 7
      src/page-instrument/header-top/index.module.less
  17. 32 11
      src/page-instrument/header-top/index.tsx
  18. 7 1
      src/page-instrument/header-top/modeView.tsx
  19. 33 4
      src/page-instrument/header-top/settting/index.tsx
  20. 4 4
      src/page-instrument/header-top/speed/index.tsx
  21. 二进制
      src/page-instrument/view-detail/images/bg2_left_zs.png
  22. 二进制
      src/page-instrument/view-detail/images/bg2_right_zs.png
  23. 15 5
      src/page-instrument/view-detail/index.module.less
  24. 56 31
      src/page-instrument/view-detail/index.tsx
  25. 0 1
      src/page-instrument/view-detail/intonationLine/index.less
  26. 1 1
      src/page-instrument/view-detail/loading.tsx
  27. 11 1
      src/page-instrument/view-detail/smoothAnimation/index.less
  28. 146 180
      src/page-instrument/view-detail/smoothAnimation/index.ts
  29. 0 0
      src/page-instrument/view-evaluat-report/component/share-top/image/audioBga.json
  30. 二进制
      src/page-instrument/view-evaluat-report/component/share-top/image/videobg.png
  31. 22 2
      src/page-instrument/view-evaluat-report/component/share-top/index.module.less
  32. 67 33
      src/page-instrument/view-evaluat-report/component/share-top/index.tsx
  33. 0 2
      src/page-instrument/view-evaluat-report/index.module.less
  34. 36 19
      src/state.ts
  35. 5 0
      src/style.css
  36. 32 0
      src/utils/index.ts
  37. 6 5
      src/view/audio-list/index.tsx
  38. 15 8
      src/view/evaluating/index.tsx
  39. 2 1
      src/view/fingering/fingering-config.ts
  40. 10 2
      src/view/fingering/index.module.less
  41. 10 1
      src/view/fingering/index.tsx
  42. 3 2
      src/view/music-score/index.tsx
  43. 10 7
      src/view/plugins/toggleMusicSheet/choosePartName/index.tsx
  44. 1 0
      src/view/plugins/useDrag/dragbom.tsx
  45. 14 12
      src/view/selection/index.module.less
  46. 39 12
      src/view/selection/index.tsx
  47. 1 1
      vite.config.ts

+ 3 - 3
instrument.html

@@ -43,14 +43,14 @@
 
 <body>
   <div id="app"></div>
-  <img id="loading" class="show" src="/loading.svg" alt="loading" />
-  <script>
+  <!-- <img id="loading" class="show" src="/loading.svg" alt="loading" /> -->
+  <!-- <script>
     // 处理课堂乐器老师端打开听音练习时去掉加载动画
     if (location.href.indexOf('view-figner') >= 0 && location.href.indexOf('platform=pc') >= 0 && location.href.indexOf('linkSource=class') < 0 && !location.href.includes("simple-detail")) {
       var _loading = document.getElementById("loading");
       _loading && document.body.removeChild(_loading);
     }
-  </script>
+  </script> -->
 
   <script type="module" src="/src/page-instrument/main.ts"></script>
   <!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>

+ 2 - 2
public/flexible.js

@@ -16,8 +16,8 @@
       f.style.fontSize = c + "px", k.rem = a.rem = c
       window.fontSize = c
     } catch (error) {
-      f.style.fontSize = 64 + "px"
-      window.fontSize = 64
+      f.style.fontSize = 37.5 + "px"
+      window.fontSize = 37.5
     }
 
   }

二进制
src/assets/DIN_Alternate_Bold.ttf


+ 45 - 6
src/helpers/customMusicScore.ts

@@ -236,6 +236,38 @@ export const moveGracePosition = (needTrans?: boolean) => {
 	}
 }
 
+// 处理一行谱高度太高的问题
+export const limitSingleSvgPageHeight = () => {
+	if (state.isSingleLine && !state.isCombineRender) {
+		// 获取生成的 SVG 元素
+		const osmdSvgPage = document.getElementById('osmdSvgPage1');
+		if (osmdSvgPage) {
+			// 获取当前的 viewBox 值
+			const viewBox = osmdSvgPage.getAttribute('viewBox');
+			if (viewBox) {
+				// 分割 viewBox 的值,viewBox 形式为 "min-x min-y width height"
+				const viewBoxValues = viewBox.split(' ');
+				// 修改 viewBox 高度
+				const newHeight = 300;
+				const staffHeight = osmdSvgPage.querySelector('.staffline')?.getBoundingClientRect()?.height;
+				if (viewBoxValues[3] > 300 && staffHeight && staffHeight < 240) {
+					const originY = state.isSimplePage ? 0 : 0.25;
+					let transY = (240 - staffHeight * state.zoom) / staffHeight;
+					transY = state.isSimplePage ? transY / 2 : transY;
+					const realY = (originY + transY) * 100 + '%';
+					viewBoxValues[3] = newHeight; // 这是第四个值,代表高度
+					// 设置新的 viewBox 值
+					osmdSvgPage.setAttribute('viewBox', viewBoxValues.join(' '));
+					osmdSvgPage.setAttribute('height','240');
+					// @ts-ignore
+					osmdSvgPage.querySelector('.staffline').style.transform = `translateY(-${realY})`
+				}
+			}
+		}
+	}
+
+}
+
 // 谱面优化
 export const resetFormate = () => {
 	container.value = document.getElementById('scrollContainer')
@@ -539,20 +571,22 @@ export const resetFormate = () => {
 
 		// 给小节添加背景色
 		if (!state.isCreateImg && !state.isPreView) {
-			staves.forEach((stave: any) => {
+			staves.forEach((stave: any,i: number) => {
 				const list = [
+					Array.from(stave?.getElementsByTagName("text") || []),
 					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?.querySelectorAll(".vf-Repetition") || []),
-					Array.from(stave?.getElementsByTagName("text") || []),
 				].flat();
 				try {
 					if (list.length) {
 						list.forEach((_el: any) => {
-							stave?.removeChild(_el)
-							_el?.style?.setProperty("display", "none");
+							if (_el.parentNode === stave) {
+								stave?.removeChild(_el)
+								_el?.style?.setProperty("display", "none");
+							}
 						});
 					}
 				} catch (error) {}
@@ -592,14 +626,19 @@ const transSinglePage = () => {
 	if (state.isSingleLine && !state.isSimplePage) {
 		const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect();
 		const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect();
-		if (svgPage && staffLine && svgPage.height > 130) {
+		if (svgPage && staffLine && svgPage.height > 200) {
 			// 需要上移的距离
-			console.log('need',svgPage.height,staffLine.height)
+			// console.log('need',svgPage.height,staffLine.height)
 			const rate = svgPage.height > 400 ? 1.2 : 2;
 			const needTransTop = (svgPage.height - staffLine.height) / rate;
 			// @ts-ignore
 			document.getElementById('osmdSvgPage1').style.transform = `translateY(-${needTransTop}px)`;
 			// document.querySelector('.staffline').style.transform = `translateY(-${needTransTop}px)`;
+			// const musicLine =  document.querySelector('.staffline').querySelector('.vf-measure').querySelector('.vf-custom-bg').getBoundingClientRect();
+			// const needTransDistance = svgPage.height / 2 - (musicLine.top - svgPage.top)
+			// console.log('svg移动距离',needTransDistance)
+			// // @ts-ignore
+			// document.getElementById('osmdSvgPage1').style.transform = `translateY(${needTransDistance}px)`
 		}
 	}
 }

+ 8 - 2
src/helpers/formateMusic.ts

@@ -970,17 +970,23 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// 	activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[0]] || [];
 			// }
 			
+			// 合并展示某些分轨,需要把展示的分轨筛选出来
+			if (state.isCombineRender && note.sourceMeasure.verticalMeasureList.length) {
+				note.sourceMeasure.verticalMeasureList = note.sourceMeasure?.verticalMeasureList.filter((item: any) => state.canSelectTracks.includes(item?.parentStaff?.parentInstrument.Name))
+			}
+
 			activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[0]] || [];
-			const currenrtVfVoices = activeVerticalMeasureList[0]?.vfVoices['1'] ? activeVerticalMeasureList[0]?.vfVoices['1'] : activeVerticalMeasureList[0]?.vfVoices['2'] ? activeVerticalMeasureList[0]?.vfVoices['2'] : null;
+			let currenrtVfVoices = activeVerticalMeasureList[0]?.vfVoices['1'] ? activeVerticalMeasureList[0]?.vfVoices['1'] : activeVerticalMeasureList[0]?.vfVoices['2'] ? activeVerticalMeasureList[0]?.vfVoices['2'] : null;
 			/**
 			 * TODO:多分轨合并的小节,音符可能没有id,此时就去其它分轨找
 			 */
 			const vmLength = note.sourceMeasure?.verticalMeasureList?.length
 			let currentVmIndex = 0;
 			let hasSvgElement = currenrtVfVoices?.tickables[staveNoteIndex];
-			while (!hasSvgElement && vmLength > 1 && currentVmIndex <= vmLength - 1) {
+			while (!hasSvgElement && vmLength > 1 && currentVmIndex <= vmLength - 1 && currenrtVfVoices !== null) {
 				currentVmIndex += 1;
 				activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[currentVmIndex]] || [];
+				currenrtVfVoices = activeVerticalMeasureList[0]?.vfVoices['1'] ? activeVerticalMeasureList[0]?.vfVoices['1'] : activeVerticalMeasureList[0]?.vfVoices['2'] ? activeVerticalMeasureList[0]?.vfVoices['2'] : null;
 				hasSvgElement = currenrtVfVoices?.tickables[staveNoteIndex];
 			}
 

+ 1 - 1
src/page-instrument/component/authorName/index.tsx

@@ -15,7 +15,7 @@ export default defineComponent({
       return () => (
          <>
             {
-               !smoothAnimationState.isShow.value && 
+               !smoothAnimationState.isShow.value && !state.isCombineRender && 
                <div class={["authorName", styles.authorName]}>
                   <div class={styles.title}>
                      <NoticeBar text={state.examSongName} background="none" />

+ 1 - 0
src/page-instrument/custom-plugins/helper-model/recommendation/index.module.less

@@ -172,6 +172,7 @@
                         font-weight: 500;
                         font-size: 14px;
                         color: #131415;
+                        caret-color: #1cacf1;
                         &::placeholder {
                             font-weight: 400;
                             font-size: 14px;

+ 8 - 0
src/page-instrument/evaluat-model/earphone/index.module.less

@@ -2,6 +2,14 @@
     position: relative;
     width: 600px;
     height: 229px;
+    &.ipadEarphoneBox{
+        width: 540px;
+        height: 206px;
+        .earphoneBtn{
+            width: 120px;
+            height: 35px;
+        }
+    }
     .earphoneBg {
         position: absolute;
         left: 50%;

+ 5 - 1
src/page-instrument/evaluat-model/earphone/index.tsx

@@ -4,6 +4,7 @@ import noEarphone from "../icons/no_erji.png";
 import youxianEarphone from "../icons/youxian_erji.png";
 import lanyaEarphone from "../icons/lanya_erji.png";
 import earphoneBtn from "../icons/confirm.png"
+import { browser } from "/src/utils";
 
 export default defineComponent({
 	name: "earphone",
@@ -15,8 +16,11 @@ export default defineComponent({
 		},
 	},	
 	setup(props, { emit }) {
+		// 资源类型
+		const browserInfo = browser();
+		const isPad =  navigator?.userAgent?.includes("UAWEIVRD-W09") || browserInfo?.iPad || browserInfo.isTablet;
 		return () => (
-			<div class={styles.earphoneBox}>
+			<div class={[styles.earphoneBox, isPad && styles.ipadEarphoneBox]}>
 				<img class={styles.earphoneBg} src={props.earphoneType === "有线耳机" ? youxianEarphone : props.earphoneType === "蓝牙耳机" ? lanyaEarphone : noEarphone} />
 				<img class={styles.earphoneBtn} src={earphoneBtn} onClick={() => emit("close")} />
 			</div>

+ 13 - 3
src/page-instrument/evaluat-model/evaluat-result/index.module.less

@@ -98,6 +98,7 @@
         .scoreSection {
             display: flex;
             align-items: flex-end;
+            justify-content: center;
         }
 
         .text {
@@ -105,8 +106,6 @@
             position: relative;
             display: flex;
             align-items: flex-end;
-            font-family: DIN-Bold, DIN;
-            font-weight: bold;
             font-weight: 600;
             font-size: 22px;
             color: #FF5510;
@@ -135,6 +134,9 @@
                 &>span {
                     margin: -2px 4px 0;
                 }
+                &.huaWeiLevel{
+                    padding-top: 3px;
+                }
             }
         }
 
@@ -142,6 +144,8 @@
             margin-right: 2px;
             margin-bottom: -2px;
             font-size: 38px;
+            font-family: DIN-Bold, DIN;
+            margin-bottom: -3px;
         }
 
         .rightBadge {
@@ -306,10 +310,16 @@
         font-weight: 500;
         font-size: 18px;
         color: #FF5510;
-        line-height: 25px;
+        display: flex;
+        align-items: flex-end;
+        line-height: 1;
         .scores{
             font-size: 14px;
         }
+        .scoresNum{
+            font-family: DIN-Bold, DIN;
+            margin-bottom: -1px;
+        }
     }
 
 }

+ 5 - 4
src/page-instrument/evaluat-model/evaluat-result/index.tsx

@@ -93,6 +93,7 @@ export default defineComponent({
       return tipContent
     })
 
+    const isHuaWeiPad = navigator?.userAgent?.includes("UAWEIVRD-W09") ? true : false
     onMounted(() => {
       if (!evaluatingData.isErrorState) {
         handleAddRecord();
@@ -128,7 +129,7 @@ export default defineComponent({
                   <div class={[styles.scoreSection, "evaluting-result-1"]}>
                     <div class={styles.num}>{evaluatingData.resultData.score}</div>
                     <div class={styles.score}>分</div>
-                    <div class={styles.level}>
+                    <div class={[styles.level, isHuaWeiPad && styles.huaWeiLevel]}>
                       <div>{level[evaluatingData.resultData.heardLevel]}</div>
                       <span>|</span>
                       <div>速度{evaluatingData.resultData.speed || state.speed}</div>
@@ -148,21 +149,21 @@ export default defineComponent({
                       <img src={yzImg} />
                       <span>音准</span>
                     </div>
-                    <div>{evaluatingData.resultData.intonation}<span class={styles.scores}>分</span></div>
+                    <div><span class={styles.scoresNum}>{evaluatingData.resultData.intonation}</span><span class={styles.scores}>分</span></div>
                   </div>
                   <div class={styles.progressitem}>
                     <div>
                       <img src={jzImg} />
                       <span>节奏</span>
                     </div>
-                    <div>{evaluatingData.resultData.cadence}<span class={styles.scores}>分</span></div>
+                    <div><span class={styles.scoresNum}>{evaluatingData.resultData.cadence}</span><span class={styles.scores}>分</span></div>
                   </div>
                   <div class={styles.progressitem}>
                     <div>
                       <img src={wzxImg} />
                       <span>完成度</span>
                     </div>
-                    <div>{evaluatingData.resultData.integrity}<span class={styles.scores}>分</span></div>
+                    <div><span class={styles.scoresNum}>{evaluatingData.resultData.integrity}</span><span class={styles.scores}>分</span></div>
                   </div>
                 </div>
               )}

+ 8 - 0
src/page-instrument/evaluat-model/index.module.less

@@ -133,6 +133,14 @@
     position: relative;
     width: 600px;
     height: 229px;
+    &.ipadEarphoneBox{
+        width: 540px;
+        height: 206px;
+        .earphoneBtn{
+            width: 120px;
+            height: 35px;
+        }
+    }
     .earphoneBg {
         position: absolute;
         left: 50%;

+ 19 - 6
src/page-instrument/evaluat-model/index.tsx

@@ -43,6 +43,8 @@ let calculateInfo: any = {};
 let checkErjiTimer: any = null
 
 export const reCheckDelay = () => {
+  evaluatingData.onceErjiPopShow = false;
+  evaluatingData.needCheckErjiStatus = true;
   headTopData.settingMode = false
   state.setting.soundEffect = false
   api_startDelayCheck({});
@@ -140,8 +142,14 @@ export default defineComponent({
         // const erji = await checkUseEarphone();
         const res = await getEarphone();
         const erji = res?.content?.checkIsWired || false;
-        console.log("耳机状态111", res);
-        evaluatingData.earphoneMode = true;
+        // 是否已经提示过耳机弹窗,重新进入评测页面,重置该状态为false,手动关掉耳机弹窗,改变该状态为true,本次评测都不在提示耳机状态弹窗
+        if (!evaluatingData.onceErjiPopShow) {
+          evaluatingData.earphoneMode = true;
+        } else {
+          clearTimeout(checkErjiTimer);
+          checkErjiTimer = null;
+          return;
+        }
         evaluatingData.earPhoneType = res?.content?.type || "";
         if (evaluatingData.earPhoneType === "有线耳机") {
           clearTimeout(checkErjiTimer);
@@ -303,7 +311,7 @@ export default defineComponent({
         speed: evaluatSpeed,
         heardLevel: state.setting.evaluationDifficulty,
         // beatLength: Math.round((state.fixtime * 1000) / rate),
-        beatLength: actualBeatLength,
+        beatLength: actualBeatLength / rate,
         evaluationCriteria: state.evaluationStandard,
         speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
       };
@@ -506,7 +514,10 @@ export default defineComponent({
         await api_startDelayCheck({});
       } else {
         evaluatingData.checkEnd = true;
-        checkEarphoneStatus();
+        // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
+        if (evaluatingData.needCheckErjiStatus) {
+          checkEarphoneStatus();
+        }
       }
       evaluatingData.isDisabledPlayMusic = true;
       // handlePerformDetection();
@@ -521,7 +532,8 @@ export default defineComponent({
 			clearTimeout(checkErjiTimer);
       checkErjiTimer = null;
 		});
-
+    // 资源类型
+    const isPad =  navigator?.userAgent?.includes("UAWEIVRD-W09") || browserInfo?.iPad || browserInfo.isTablet;
     return () => (
       <div>
         <div class={styles.operatingBtn}>
@@ -575,7 +587,7 @@ export default defineComponent({
           evaluatingData.isBeginMask && <div class={styles.beginMask}></div>
         }
         <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={tipErjiPopShow.value}>
-          <div class={styles.earphoneBox}>
+          <div class={[styles.earphoneBox, isPad && styles.ipadEarphoneBox]}>
             <img class={styles.earphoneBg} src={tipErjiBg} />
             <img class={styles.earphoneBtn} src={tipErjiBtn} onClick={() => {
               evaluatingData.tipErjiShow = false;
@@ -587,6 +599,7 @@ export default defineComponent({
           <Earphone
             earphoneType={evaluatingData.earPhoneType}
             onClose={() => {
+              evaluatingData.onceErjiPopShow = true;
               clearTimeout(checkErjiTimer);
               checkErjiTimer = null;
               // #11035,可能刚好关闭耳机弹窗的时候,第二次又出现了弹窗

+ 12 - 11
src/page-instrument/follow-model/microphone/index.module.less

@@ -54,19 +54,20 @@
         height: 60%;
         transform: translateX(-50%);
     }
-    .microCancel {
+    .microBtn {
         position: absolute;
-        bottom: 85px;
-        left: 39.5%;
-        width: 91px;
-        height: 39px;
+        bottom: 23.5%;
+        left: 50%;
+        transform: translate(-45%);
+    }
+    .microCancel {
+        width: 11.2vw;
+        height: 100%;
+        margin-right: 2px;
     }
     .microConfirm {
-        position: absolute;
-        bottom: 85px;
-        right: 28.5%;
-        transform: translateX(-80%);
-        width: 91px;
-        height: 39px;
+        width: 11.2vw;
+        height: 100%;
+        margin-left: 2px;
     }
 }

+ 4 - 2
src/page-instrument/follow-model/microphone/index.tsx

@@ -14,8 +14,10 @@ export default defineComponent({
 		return () => (
 			<div class={styles.microBox}>
 				<img class={styles.microBg} src={microBg} />
-				<img class={styles.microCancel} src={microCancel} onClick={() => emit("close")} />
-				<img class={styles.microConfirm} src={microConfirm} onClick={() => emit("close")} />
+				<div class={styles.microBtn}>
+					<img class={styles.microCancel} src={microCancel} onClick={() => emit("close")} />
+					<img class={styles.microConfirm} src={microConfirm} onClick={() => emit("close")} />
+				</div>
 			</div>
 		);
 	},

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

@@ -11,6 +11,15 @@
         justify-content: flex-end;
     }
 }
+.headerMid {
+    background: transparent;
+    width: 40%;
+    height: 100%;
+    position: absolute;
+    left: 50%;
+    bottom: 0;
+    transform: translateX(-40%);
+}
 .modeWarn{
     position: fixed;
     left: 30px;
@@ -108,6 +117,8 @@
     display: flex;
     align-items: center;
     height: 100%;
+    position: relative;
+    z-index: 9;
     .btn {
         position: relative;
         display: flex;
@@ -228,6 +239,7 @@
             top: -9px;
             display: flex;
             align-items: center;
+            justify-content: center;
             background: #FFC121;
             border-radius: 120px 120px 120px 1px;
             border: 1px solid #FFFFFF;
@@ -240,7 +252,7 @@
                 font-weight: 600;
                 font-size: 12px;
                 color: #673207;
-                line-height: 1;
+                line-height: 16px;
             }
         }
     }
@@ -267,13 +279,13 @@
         }
     }
     &.playLeftButton {
-        left: 46px !important;
+        left: 30px !important;
         right: auto !important;
         bottom: 12px !important;
     }
 
     &.playRightButton {
-        right: 46px !important;
+        right: 30px !important;
         left: auto !important;
         bottom: 12px !important;
     }
@@ -301,13 +313,13 @@
     }
 
     &.pauseLeftButton {
-        left: 108px !important;
+        left: 102px !important;
         right: auto !important;
         bottom: 12px !important;
     }
 
     &.pauseRightButton {
-        right: 108px !important;
+        right: 102px !important;
         left: auto !important;
         bottom: 12px !important;
     }
@@ -337,7 +349,7 @@
     width: 100vw;
     height: 100vh;
     background: url(./image/bg.png) no-repeat;
-    background-size: 100% 100%;
+    background-size: cover;
     transition: all .3s;
     &.hidden{
         opacity: 0;
@@ -362,10 +374,12 @@
     }
     .modeBox {
         width: 100%;
-        margin-top: 90px;
         display: flex;
         justify-content: space-between;
         padding: 0 36px;
+        position: relative;
+        top: 50%;
+        transform: translateY(-50%);
         &.twoModeBox{
             justify-content: center;
             > .modeImg + .modeImg{

+ 32 - 11
src/page-instrument/header-top/index.tsx

@@ -1,4 +1,4 @@
-import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef, ComputedRef, nextTick } from "vue";
+import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef, ComputedRef, nextTick, defineAsyncComponent } from "vue";
 import styles from "./index.module.less";
 
 import iconBack from "./image/icon-back.png";
@@ -28,10 +28,15 @@ import { toggleMusicSheet } from "/src/view/plugins/toggleMusicSheet";
 import useDrag from "/src/view/plugins/useDrag/index";
 import Dragbom from "/src/view/plugins/useDrag/dragbom";
 import { getGuidance, setGuidance } from "../custom-plugins/guide-page/api";
-import ModeView from "./modeView";
+// import ModeView from "./modeView";
 import { smoothAnimationState } from "../view-detail/smoothAnimation";
 import { isMusicList, musicListShow } from "../component/the-music-list";
 import { EvaluatingDriver, FollowDriver, PractiseDriver } from "../custom-plugins/guide-driver";
+import { fingerRef } from "/src/page-instrument/view-detail/index"
+
+const ModeView = defineAsyncComponent(() =>
+  import('./modeView')
+)
 
 /** 头部数据和方法 */
 export const headTopData = reactive({
@@ -293,7 +298,8 @@ export default defineComponent({
     /** 速度按钮 */
     const speedBtn = computed(() => {
       // 选择模式, 跟练模式  不显示
-      if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: true };
+      //if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: true };
+      if (state.modeType === "follow") return { display: false, disabled: true };
       // 评测模式, 音频播放中 禁用
       if (state.modeType === "evaluating" || state.playState === "play") return { display: true, disabled: true };
 
@@ -305,7 +311,7 @@ export default defineComponent({
     /** 节拍器按钮 */
     const metronomeBtn = computed(() => {
       // 选择模式  不显示
-      if (headTopData.modeType !== "show") return { display: false, disabled: true };
+      //if (headTopData.modeType !== "show") return { display: false, disabled: true };
       // 音频播放中 禁用
       if (state.playState === "play") return { display: true, disabled: true };
       return {
@@ -319,7 +325,8 @@ export default defineComponent({
       // 后台设置不显示指法
       if (!state.isShowFingering) return { display: true, disabled: true };
       // 没有指法 选择模式 评测模式 跟练模式 不显示
-      if (headTopData.modeType !== "show" || !state.fingeringInfo.name || ["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
+      //if (headTopData.modeType !== "show" || !state.fingeringInfo.name || ["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
+      if (!state.fingeringInfo.name || ["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
       // 音频播放中 禁用
       if (state.playState === "play") return { display: true, disabled: true };
 
@@ -345,7 +352,8 @@ export default defineComponent({
     /** 选段按钮 */
     const selectBtn = computed(() => {
       // 选择模式 不显示
-      if (headTopData.modeType !== "show" || ["follow"].includes(state.modeType)) return { display: false, disabled: true };
+      //if (headTopData.modeType !== "show" || ["follow"].includes(state.modeType)) return { display: false, disabled: true };
+      if (["follow"].includes(state.modeType)) return { display: false, disabled: true };
       // 音频播放中 禁用
       if (state.playState === "play" || query.workRecord) return { display: true, disabled: true };
 
@@ -360,7 +368,8 @@ export default defineComponent({
       // 没有音源不显示
       if(state.noMusicSource) return { display: false, disabled: false };
       // 选择模式,跟练模式 不显示
-      if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: false };
+      //if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: false };
+      if (state.modeType === "follow") return { display: false, disabled: false };
       // 评测开始 禁用
       if (state.modeType === "evaluating") return { display: false, disabled: true };
       if (!state.isAppPlay) {
@@ -390,7 +399,8 @@ export default defineComponent({
     /** 播放类型按钮 */
     const playTypeBtn = computed(() => {
       // 选择模式,跟练模式,评测模式 不显示
-      if (headTopData.modeType !== "show" || state.modeType === "follow" || state.modeType === "evaluating") return { display: false, disabled: false };
+      //if (headTopData.modeType !== "show" || state.modeType === "follow" || state.modeType === "evaluating" || query.workRecord) return { display: false, disabled: false };
+      if (state.modeType === "follow" || state.modeType === "evaluating" || query.workRecord) return { display: false, disabled: false };
       if (!state.isAppPlay) {
         let index = 0;
         state.music && index++;
@@ -415,6 +425,9 @@ export default defineComponent({
     });
     /** 模式切换按钮 */
     const toggleBtn = computed(() => {
+      // 老师端,打击乐&节奏练习不显示
+      if (state.isPercussion && state.platform === IPlatform.PC) return { display: false, disabled: false };
+      if(state.isCombineRender) return { display: false, disabled: false };
       // 没有音源不显示
       if(state.noMusicSource) return { display: false, disabled: false };
       // 不是演奏模式 影藏
@@ -543,7 +556,7 @@ export default defineComponent({
     const changePlay = (res: any) => {
       // console.log('监听上课页面message',res)
       if (res?.data?.api === "setPlayState") {
-        togglePlay("paused", "courseware");
+        togglePlay("paused", true);
       }
       // 上课页面,按钮方向
       if (res?.data?.api === "imagePos") {
@@ -584,7 +597,7 @@ export default defineComponent({
     const noticeBarWidth = ref<number>()
     watch(()=>smoothAnimationState.isShow.value, ()=>{
       // NoticeBar能不能滚动
-      if(smoothAnimationState.isShow.value && isMusicList.value){
+      if((smoothAnimationState.isShow.value || state.isCombineRender) && isMusicList.value){
         nextTick(()=>{
           const widthCon = (document.querySelector("#noticeBarRollDom .van-notice-bar__content") as any)?.offsetWidth || undefined
           noticeBarWidth.value = widthCon
@@ -646,7 +659,7 @@ export default defineComponent({
               <div id="noticeBarRollDom" class={styles.headTopLeftBox}>
                 <img src={iconBack} class={['headTopBackBtn', styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
                 {
-                  smoothAnimationState.isShow.value ?
+                  smoothAnimationState.isShow.value || state.isCombineRender ?
                     <div 
                       style={
                         noticeBarWidth.value ? {
@@ -873,6 +886,14 @@ export default defineComponent({
           </div>
         </div>
 
+        {/** 指法点击区域 */} 
+        {
+          state.fingeringInfo.direction === "transverse" && state.setting.displayFingering ? 
+          <div class={styles.headerMid} onClick={() => {
+            fingerRef.value?.doubeClick()
+          }}></div>  : null
+        }
+
         {/* 播放按钮 */}
         <div
           id="studnetT-7"

+ 7 - 1
src/page-instrument/header-top/modeView.tsx

@@ -110,6 +110,8 @@ export default defineComponent({
             smoothAnimationState.isShow.value = state.melodyLine;
             // 返回的时候 跳转到之前记录的模式
             if(headTopData.oldModeType !== "practise"){
+              // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
+              evaluatingData.needCheckErjiStatus = false;
               headTopData.handleChangeModeType(headTopData.oldModeType)
             }
             headTopData.modeType = "show";
@@ -122,7 +124,11 @@ export default defineComponent({
             headTopData.handleChangeModeType("practise")
             } }></Vue3Lottie>
           {!state.isPercussion && <Vue3Lottie ref={modeImgDom2} class={styles.modeImg} animationData={glMode} autoPlay={false} loop={true} onClick={() => headTopData.handleChangeModeType("follow")}></Vue3Lottie>}
-          {state.enableEvaluation && <Vue3Lottie ref={modeImgDom3} class={styles.modeImg} animationData={pcMode} autoPlay={false} loop={true} onClick={() => headTopData.handleChangeModeType("evaluating")}></Vue3Lottie>}
+          {state.enableEvaluation && <Vue3Lottie ref={modeImgDom3} class={styles.modeImg} animationData={pcMode} autoPlay={false} loop={true} onClick={() => {
+            // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
+            evaluatingData.needCheckErjiStatus = true;
+            headTopData.handleChangeModeType("evaluating")
+          }}></Vue3Lottie>}
         </div>
         {data.showVip && <TheVip />}
         {/** 延迟检测中途,socket出错,网络提示弹窗 */}

+ 33 - 4
src/page-instrument/header-top/settting/index.tsx

@@ -1,9 +1,9 @@
-import { defineComponent, reactive, computed } from "vue";
+import { defineComponent, reactive, computed, toRef } from "vue";
 import styles from "./index.module.less"
 import { headImg } from "../image";
 import { headTopData } from "../index"
 import { Switch, showToast, Field, Popup, Slider } from "vant";
-import state, { refreshMusicSvg } from "/src/state"
+import state, { refreshMusicSvg, IPlatform } from "/src/state"
 import { api_closeCamera, api_openCamera, api_savePicture } from "/src/helpers/communication";
 import { smoothAnimationState} from "/src/page-instrument/view-detail/smoothAnimation"
 import Recommendation from "../../custom-plugins/helper-model/recommendation";
@@ -12,6 +12,10 @@ import ScreenModel from "../../custom-plugins/helper-model/screen-model";
 import { getQuery } from "/src/utils/queryString";
 import { reCheckDelay } from "/src/page-instrument/evaluat-model"
 import { audioData, changeMingSongType } from "/src/view/audio-list"
+import useDrag from "/src/view/plugins/useDrag/index";
+import Dragbom from "/src/view/plugins/useDrag/dragbom";
+import { storeData } from "/src/store";
+import { getGuidance, setGuidance } from "../../custom-plugins/guide-page/api";
 
 export default defineComponent({
 	name: "settting",
@@ -21,6 +25,25 @@ export default defineComponent({
 			screenModelShow: false, // 投屏帮助
 			recommendationShow: false, // 建议
 		});
+		const parentClassName = "recommenBoxClass_drag";
+		const userId = storeData.user?.id ? String(storeData.user?.id) : "";
+		const positionInfo =
+		  state.platform !== IPlatform.PC
+			? {
+				styleDrag: { value: null },
+			  }
+			: useDrag([`${parentClassName} .top_drag`, `${parentClassName} .bom_drag`], parentClassName, toRef(helperData, "recommendationShow"), userId);
+
+		// 完成拖动弹窗引导页
+		const handleGuide = async () => {
+			state.guideInfo.teacherDrag = true;
+			try {
+			const res = await setGuidance({ guideTag: "guideInfo", guideValue: JSON.stringify(state.guideInfo) });
+			} catch (e) {
+			console.log(e);
+			}
+		};
+
         // 加减评测频率
 		const operateHz = (type: number) => {
 			const minFrequency = state.baseFrequency - 10, maxFrequency = state.baseFrequency + 10
@@ -50,7 +73,7 @@ export default defineComponent({
                 <div class={styles.content}>
                     <div class={styles.conBox}>
                         {
-                            state.isShowFingering && state.fingeringInfo.name && ["practise", "follow"].includes(state.modeType) && state.playType === "play" &&
+                            state.isShowFingering && state.fingeringInfo.name && ["practise", "follow", "evaluating"].includes(state.modeType) && state.playType === "play" &&
                                 <div class={styles.cellBox}>
                                 <div class={styles.tit}>指法</div>
                                     <Switch v-model={state.setting.displayFingering}></Switch>
@@ -122,16 +145,20 @@ export default defineComponent({
                                 <div class={[styles.cellBox, state.setting.camera && styles.isCamera]}>
                                     <div class={styles.tit}>摄像头</div>
                                     <Switch 
-                                        v-model={state.setting.camera}
+                                        // v-model={state.setting.camera}
+                                        modelValue={state.setting.camera}
                                         onChange={ async (value) => {
                                             if (value) {
                                                 const res = await api_openCamera();
                                                 // 没有授权
                                                 if (res?.content?.reson) {
                                                     state.setting.camera = false
+                                                } else {
+                                                    state.setting.camera = true
                                                 }
                                             } else {
                                                 api_closeCamera();
+                                                state.setting.camera = false;
                                             }
                                         }}                                
                                     ></Switch>
@@ -236,12 +263,14 @@ export default defineComponent({
 					transition="van-scale"
 					teleport="body"
                     overlay-style={{background:'rgba(0, 0, 0, 0.3)'}}
+                    style={positionInfo.styleDrag.value}
 				>
                     <Recommendation
                         onClose={() => {
                             helperData.recommendationShow = false;
                         }}
                     />
+                    {state.platform === IPlatform.PC && <Dragbom showGuide={!state.guideInfo?.teacherDrag} onGuideDone={handleGuide} />}
 				</Popup>
                 <Popup
 					class={["popup-custom"]}

+ 4 - 4
src/page-instrument/header-top/speed/index.tsx

@@ -66,7 +66,7 @@ export default defineComponent({
 			  if (storeData.isApp && state.enableEvaluation) {
 				// 加载弹窗的开始时间
 				let startTime = +new Date();
-				state.loadingText = '节拍器准备中,请稍等…'
+				state.loadingText = res ? '节拍器准备中,请稍等…' : '节拍器关闭中,请稍等…';
 				state.isLoading = true;
 				const targetSrc = res ? state.beatSong.accompany || state.beatSong.music : state.accompany || state.music;
 				const resData = await api_updateMusicPlayer({
@@ -89,7 +89,7 @@ export default defineComponent({
 							state.isLoading = false;
 							metronomeDisable.value = res;
 							switchLoading.value = false;
-						}, continueTime);
+						}, 1200);
 					}
 				}
 				// api_checkSocketStatus();
@@ -100,8 +100,8 @@ export default defineComponent({
 			} catch (error) {
 			  console.log(error)
 			} finally {
-				state.isLoading = false;
-				switchLoading.value = false;
+				// state.isLoading = false;
+				// switchLoading.value = false;
 			}
 		};
 		return () => (

二进制
src/page-instrument/view-detail/images/bg2_left_zs.png


二进制
src/page-instrument/view-detail/images/bg2_right_zs.png


+ 15 - 5
src/page-instrument/view-detail/index.module.less

@@ -60,12 +60,18 @@
 
     .container {
         position: sticky;
-        top: 36px;
-        height: calc(100vh - 36px);
+        top: 0;
+        height: 100vh;
         border-radius: 10px;
         transition: height .2s;
         transition: padding-bottom .2s;
         overflow: hidden;
+        :global{
+            #musicAndSelection {
+                // 其他位置 这个高度留白是36,这里加了一点,让旋律线靠下一点
+                padding-top: 40px;
+            }
+        }
     }
     .pcContainer {
         // height: calc(100vh - var(--header-height) - var(--pc-header-height));
@@ -229,6 +235,12 @@
     align-items: center;
     z-index: 10000;
     background: rgba(0, 0, 0, .6);
+    &.isPreView{
+        background:transparent;
+        .loadingTip{
+            color: #999;
+        }
+    }
     .lottie{
         width: 120px;
     }
@@ -240,14 +252,12 @@
 
 .bg2Left {
     width: 52px;
-    height: 125px;
     position: absolute;
-    left: 0;
+    left: 0px;
     top: 0;
 }
 .bg2Right {
     width: 52px;
-    height: 125px;
     position: absolute;
     right: 0;
     top: 0;

+ 56 - 31
src/page-instrument/view-detail/index.tsx

@@ -38,7 +38,7 @@ import { initSmoothAnimation } from "./smoothAnimation"
 import EmptyMusic, { isEmptyMusicShow } from "./emptyMusic"
 import { position } from "html2canvas/dist/types/css/property-descriptors/position";
 import Loading from "./loading"
-import bgJson from "./images/index.json";
+// import bgJson from "./images/index.json";
 import bg2Left from "./images/bg2_left_zs.png";
 import bg2Right from "./images/bg2_right_zs.png";
 
@@ -85,6 +85,9 @@ const setNoteHalfTone = (list: any[]) => {
 };
 
 export const musicScoreRef = ref();
+export const fingerRef = ref();
+
+export const headerColumnHide = ref(false); // 是否隐藏功能按钮,播放时自动隐藏
 
 export default defineComponent({
   name: "music-list",
@@ -141,6 +144,20 @@ export default defineComponent({
     };
     // console.log(route.params, query)
 
+    // 动态加载json背景
+    const bgJsonData: any = ref(null);
+    const jsonFiles: any = {
+      data: () => import('./images/index.json'),
+    };
+    const loadJson = async (fileKey: any) => {
+      try {
+         const data = await jsonFiles[fileKey]();
+        bgJsonData.value = data.default;
+      } catch (error) {
+        console.error('👀~json背景加载失败', error);
+      }
+    };
+
     onMounted(async () => {
       (window as any).appName = "colexiu";
       const id = query.id || "43554";
@@ -157,6 +174,7 @@ export default defineComponent({
       // Promise.all([sysMusicScoreAccompanimentQueryPage(id)]).then((values) => {
       //   getMusicInfo(values[0]);
       // });
+      loadJson('data');
       try { 
         await getMusicDetail(id);
       } catch (err) {
@@ -166,8 +184,8 @@ export default defineComponent({
         return
       }
       detailData.isLoading = false;
-      // 如果后台设置了不显示指法,关闭指法开关
-      if (!state.isShowFingering) {
+      // 如果后台设置了不显示指法,关闭指法开关   如果默认进来是演奏模式 不显示指法开关
+      if (!state.isShowFingering || state.playType === "sing") {
         state.setting.displayFingering = false
       }      
       // api_setEventTracking();
@@ -271,8 +289,8 @@ export default defineComponent({
         handleInitTick(osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Numerator || 4);
       // }
       // api_cloudLoading();
-      state.playBtnDirection = query.imagePos === 'left' ? 'left' : 'right';
-      state.isAttendClass = (query.imagePos === 'left' || query.imagePos === 'right') ? true : false;
+      // state.playBtnDirection = query.imagePos === 'left' ? 'left' : 'right';
+      // state.isAttendClass = (query.imagePos === 'left' || query.imagePos === 'right') ? true : false;
       // if (state.fingeringInfo.direction === "vertical" && state.setting.displayFingering) {
       //   state.musicScoreBtnDirection = state.playBtnDirection === 'right' ? 'left' : 'right';
       // } else {
@@ -296,21 +314,18 @@ export default defineComponent({
         if (state.fingeringInfo.direction === "transverse") {
           return {
             container: {
-              paddingBottom: detailData.headerHide ? state.fingeringInfo.height : state.fingeringInfo.scaleData?.offset
+              //paddingBottom: headerColumnHide.value ? state.fingeringInfo.height : state.fingeringInfo.scaleData?.offset
+              paddingBottom: state.fingeringInfo.height
             },
             // 横向指法,跟练&评测模式,默认展示贴底展示
-            fingerBox: (state.modeType === 'follow' || state.modeType === 'evaluating') ? 
-            {
-              height: state.fingeringInfo.height,
-              position: 'absolute',
-              bottom: 0,
-              width: '100%'
-            } : 
-            detailData.headerHide ? {
+            // fingerBox: headerColumnHide.value ? {
+            //     height: state.fingeringInfo.height
+            //   } : {
+            //     height: state.fingeringInfo.height,
+            //     transform: `scale(${state.fingeringInfo.scaleData?.scale})`
+            //   }
+            fingerBox: {
               height: state.fingeringInfo.height
-            } : {
-              height: state.fingeringInfo.height,
-              transform: `scale(${state.fingeringInfo.scaleData?.scale})`
             }
           };
         } else {
@@ -328,7 +343,7 @@ export default defineComponent({
                 height: state.fingeringInfo.name === "hulusi-flute" ? "86%" : "80%",
                 right: state.playBtnDirection === "right" ? "initial" : 0,
                 left: state.playBtnDirection === "right" ? 0 : "initial",
-                top: state.fingeringInfo.name === "ocarina" ? "60px" : 0,
+                top: (state.fingeringInfo.name === "ocarina" || state.fingeringInfo.name === "whistling") ? "60px" : state.fingeringInfo.name === "hulusi-flute" ? "10px" : (state.fingeringInfo.name === "baroque-recorder" || state.fingeringInfo.name === "piccolo") ? "36px" : 0,
               },
             };
           } else {
@@ -341,7 +356,7 @@ export default defineComponent({
                 width: state.fingeringInfo.width,
                 height: state.fingeringInfo.name === "hulusi-flute" ? "86%" : "80%",
                 left: 0,
-                top: state.fingeringInfo.name === "ocarina" ? "60px" : 0,
+                top: (state.fingeringInfo.name === "ocarina" || state.fingeringInfo.name === "whistling") ? "60px" : state.fingeringInfo.name === "hulusi-flute" ? "10px" : (state.fingeringInfo.name === "baroque-recorder" || state.fingeringInfo.name === "piccolo") ? "36px" : 0,
               },
             };
           }
@@ -389,9 +404,9 @@ export default defineComponent({
       () => state.playState,
       () => {
         // if (state.platform != IPlatform.PC) {
-        //   detailData.headerHide = state.playState === "play" ? true : false;
+        //   headerColumnHide.value = state.playState === "play" ? true : false;
         // }
-        detailData.headerHide = state.playState === "play" ? true : false;
+        headerColumnHide.value = state.playState === "play" ? true : false;
         sendParentMessage(state.playState);
       }
     );
@@ -399,9 +414,16 @@ export default defineComponent({
     watch(
       () => followData.start,
       () => {
-        detailData.headerHide = followData.start;
+        headerColumnHide.value = followData.start;
       }
     );
+    // 监听开始评测状态
+    watch(
+      () => evaluatingData.startBegin,
+      () => {
+        headerColumnHide.value = evaluatingData.startBegin;
+      }
+    );    
     /** 指法预览切换 */
     watch(
       () => detailData.fingerPreView,
@@ -470,11 +492,13 @@ export default defineComponent({
           background: state.setting.camera ? `rgba(${state.setting.eyeProtection ? "253,244,229" : "255,255,255"} ,${state.setting.cameraOpacity / 100}) !important` : "",
         }}
       >
-        <img 
-          style={{opacity: state.setting.camera && state.modeType === 'evaluating' ? state.setting.cameraOpacity / 100 : 1}} 
-          class={styles.pageBg} 
-          src={state.modeType === 'practise' ? bgJson[1] : state.modeType === 'evaluating' ? bgJson[2] : state.modeType === 'follow' ? bgJson[3] : ''} 
-        />
+        {bgJsonData.value ? 
+          <img 
+            style={{opacity: state.setting.camera && state.modeType === 'evaluating' ? state.setting.cameraOpacity / 100 : 1}} 
+            class={styles.pageBg} 
+            src={state.modeType === 'practise' ? bgJsonData.value[1] : state.modeType === 'evaluating' ? bgJsonData.value[2] : state.modeType === 'follow' ? bgJsonData.value[3] : ''} 
+          /> : null    
+        }
         {
           state.modeType === 'evaluating' ? <>
             <img src={bg2Left} class={styles.bg2Left} />
@@ -494,7 +518,7 @@ export default defineComponent({
         {/** 功能按钮 */}
         {
           !state.isPreView && 
-          <div class={[styles.headHeight, detailData.headerHide && styles.headHide]}>{state.musicRendered && <HeaderTop />}</div>
+          <div class={[styles.headHeight, headerColumnHide.value && styles.headHide]}>{state.musicRendered && <HeaderTop />}</div>
         }
         <div
           id="scrollContainer"
@@ -503,11 +527,11 @@ export default defineComponent({
           onClick={(e: Event) => {
             e.stopPropagation();
             // if (state.playState === "play" && state.platform != IPlatform.PC) {
-            //   detailData.headerHide = !detailData.headerHide;
+            //   headerColumnHide.value = !headerColumnHide.value;
             // }
             // 点击谱面跟练也需要切换显示按钮栏
-            if (state.playState === "play" || followData.start) {
-              detailData.headerHide = !detailData.headerHide;
+            if (state.playState === "play" || followData.start || evaluatingData.startBegin) {
+              headerColumnHide.value = !headerColumnHide.value;
             }
           }}
         >
@@ -535,6 +559,7 @@ export default defineComponent({
           {state.setting.displayFingering && state.fingeringInfo?.name && !state.isPreView && state.isShowFingering && (
             <div style={{ ...fingerConfig.value.fingerBox }} class={styles.fingeringCon}>
               <Fingering
+                ref={fingerRef}
                 style={{
                   background: state.setting.camera ? `rgba(${state.setting.eyeProtection ? "253,244,229" : "255,255,255"} ,${state.setting.cameraOpacity / 100})` : "",
                 }}

+ 0 - 1
src/page-instrument/view-detail/intonationLine/index.less

@@ -2,7 +2,6 @@
     .intonationLineBox{
         display: flex;
         align-items: center;
-        height: 2.4rem;
     }
     .intonationLineCon{
         position: relative;

+ 1 - 1
src/page-instrument/view-detail/loading.tsx

@@ -18,7 +18,7 @@ export default defineComponent({
    setup(props) {
       return () =>
          (
-            <div class={styles.loadingPop} style={{display:state.isLoading? "flex" : "none"}}>
+            <div class={[styles.loadingPop, state.isPreView && styles.isPreView]} style={{display:state.isLoading? "flex" : "none"}}>
                <img class={styles.lottie} src={animGif} />
                {/* <Vue3Lottie class={styles.lottie} animationData={animBg}></Vue3Lottie> */}
                <div class={styles.loadingTip}>{props.tipText}</div>

+ 11 - 1
src/page-instrument/view-detail/smoothAnimation/index.less

@@ -1,8 +1,14 @@
+
+// 有时候 singleLineMusicBox 还没有赋值所以导致 旋律线和小鸟显示,所以这里默认隐藏
+#musicAndSelection{
+    .smoothAnimationBox{
+        display: none;
+    }
+}
 #musicAndSelection.singleLineMusicBox{
     .smoothAnimationBox{
         display: flex;
         align-items: center;
-        height: 2.4rem;
         &.smoothAnimationBoxHide{
             opacity: 0;
             visibility: hidden;
@@ -17,6 +23,9 @@
             left: 0;
             top: 0;
         }
+        .smoothCanvas{
+            display: block;
+        }
     }
     #osmdCanvasPage1{
         top: 0;
@@ -26,6 +35,7 @@
     }
     .authorName{
         position: fixed;
+        // position: absolute;
         left: 0;
         top: 36px;
         width: 100vw;

+ 146 - 180
src/page-instrument/view-detail/smoothAnimation/index.ts

@@ -7,13 +7,14 @@ import state from "/src/state"
 import "./index.less"
 import Bird from "./bird"
 
-type pointsPosType = { x: number; y: number; MeasureNumberXML: number }[]
+type pointsPosType = { x: number; y: number; MeasureNumberXML: number; noteId: number }[]
 type smoothAnimationType = {
    isShow: Ref<boolean>
    canvasDom: null | HTMLCanvasElement
    canvasCtx: null | undefined | CanvasRenderingContext2D
    canvasDomWith: number
    canvasDomHeight: number
+   canvasSmoothDom: null | HTMLCanvasElement
    smoothAnimationBoxDom: null | HTMLElement
    smoothBotDom: null | HTMLElement
    osmdCanvasPageDom: null | HTMLElement
@@ -21,19 +22,22 @@ type smoothAnimationType = {
    osdmScrollDomWith: number
    osdmScrollDomOffsetLeft: number
    selectionBoxDom: null | HTMLElement
+   batePos: pointsPosType
    pointsPos: pointsPosType
    translateXNum: number
    aveSpeed: number
 }
 
-const _numberOfSegments = 58 // 中间切割线的个数
+const _numberOfSegments = 60 // 中间切割线的个数
+const _canvasDomHeight = 60 // canvans 高度
 
 export const smoothAnimationState = {
    isShow: ref(false), // 是否显示
    canvasDom: null,
    canvasCtx: null,
    canvasDomWith: 0,
-   canvasDomHeight: 80,
+   canvasDomHeight: _canvasDomHeight,
+   canvasSmoothDom: null,
    smoothAnimationBoxDom: null,
    smoothBotDom: null,
    osmdCanvasPageDom: null,
@@ -41,7 +45,8 @@ export const smoothAnimationState = {
    osdmScrollDomWith: 0,
    osdmScrollDomOffsetLeft: 0,
    selectionBoxDom: null,
-   pointsPos: [], // 计算之后的点坐标数组
+   batePos: [], // times 直接转换的数组
+   pointsPos: [], // 筛选之后的点坐标数组
    translateXNum: 0, // 当前谱面的translateX的距离   谱面的位置信息 由translateX和scrollLeft的偏移一起决定
    aveSpeed: 0 // 谱面的一帧的平均速度
 } as smoothAnimationType
@@ -63,8 +68,12 @@ export function initSmoothAnimation() {
    createSmoothAnimation()
    // 初始化动画数据
    const batePos = getPointsPosByBatePos()
-   console.log(batePos, "batePos")
-   smoothAnimationState.pointsPos = createSmoothCurvePoints(batePos, undefined, undefined, _numberOfSegments)
+   smoothAnimationState.batePos = batePos
+   const batePos1 = dataFilter([...batePos])
+   const batePos2 = createSmoothCurvePoints(batePos1, _numberOfSegments)
+   smoothAnimationState.pointsPos = batePos2
+   // 初始化旋律线
+   initCanvasSmooth()
    // 谱面的平均速度(因为可能有反复的情况所以实际距离要加上反复的距离)
    const canvasDomPath = batePos.reduce((path, item, index, arr) => {
       if (index !== 0) {
@@ -88,6 +97,30 @@ export function initSmoothAnimation() {
    console.log(smoothAnimationState, "一行谱小鸟数据")
 }
 
+// 排序
+function dataFilter(batePos: pointsPosType) {
+   // 去掉反复跳房子的数据
+   const filterData = batePos.filter((item, index, array) => {
+      return array.findIndex(i => i.noteId === item.noteId) === index
+   })
+   // 先按 音符排序
+   const sortedData = filterData.sort((a, b) => a.noteId - b.noteId)
+   // 再按 小节排序
+   return sortedData.sort((a, b) => a.MeasureNumberXML - b.MeasureNumberXML)
+}
+
+//根据 activeInde和进度 查找转换之后的activeInde
+function dataFindIndex(activeIndex: number, progress: number) {
+   // 百分比转为当前的index 距离个数
+   const progressCalcIndex = Math.round(progress * _numberOfSegments)
+   const { noteId, MeasureNumberXML } = smoothAnimationState.batePos[activeIndex]
+   return (
+      smoothAnimationState.pointsPos.findIndex(item => {
+         return item.noteId === noteId && item.MeasureNumberXML === MeasureNumberXML
+      }) + progressCalcIndex
+   )
+}
+
 /**
  * 销毁
  */
@@ -99,7 +132,8 @@ export function destroySmoothAnimation() {
       canvasDom: null,
       canvasCtx: null,
       canvasDomWith: 0,
-      canvasDomHeight: 80,
+      canvasDomHeight: _canvasDomHeight,
+      canvasSmoothDom: null,
       smoothAnimationBoxDom: null,
       smoothBotDom: null,
       osmdCanvasPageDom: null,
@@ -107,6 +141,7 @@ export function destroySmoothAnimation() {
       osdmScrollDomWith: 0,
       osdmScrollDomOffsetLeft: 0,
       selectionBoxDom: null,
+      batePos: [],
       pointsPos: [],
       translateXNum: 0,
       aveSpeed: 0
@@ -125,12 +160,7 @@ export function moveSmoothAnimationByPlayTime(time?: number) {
    if (currentTime <= state.fixtime) return
    if (currentTime > state.times.last()?.endtime) return
    // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
-   let nextIndex = state.activeNoteIndex + 1
-   let nextBBox = state.times[nextIndex]?.bbox
-   while (!nextBBox && nextIndex < state.times.length) {
-      nextIndex += 1
-      nextBBox = state.times[nextIndex]?.bbox
-   }
+   const nextIndex = state.activeNoteIndex + 1
    // 当前的音符和下一个音符之间的时值 (当是最后一个音符的时候,下一个音符的时间取当前音符的endtime)
    const noteDuration =
       (nextIndex > state.times.length - 1 ? state.times[state.activeNoteIndex]?.endtime : state.times[nextIndex].time) -
@@ -155,21 +185,19 @@ export function moveSmoothAnimation(progress: number, activeIndex: number, isMov
    // if (!smoothAnimationState.isShow.value) {
    //    return
    // }
-   // 计算 下一个音符index 在pointsPos 中的距离
-   const nextPointsIndex = (activeIndex + 1) * (_numberOfSegments + 1) - 1
-   // 百分比转为当前的index 距离个数
-   const progressCalcIndex = Math.round(progress * _numberOfSegments)
-   // // 当前的index
-   let nowIndex = nextPointsIndex - _numberOfSegments + progressCalcIndex
+   const nowIndex = dataFindIndex(activeIndex, progress)
    const nowPointsPos = smoothAnimationState.pointsPos[nowIndex]
-   smoothAnimationState.canvasCtx?.clearRect(0, 0, smoothAnimationState.canvasDomWith, smoothAnimationState.canvasDomHeight)
+   // 当x的值为null和undefinedde的时候 错误 不走下面的方法
+   if (!(nowPointsPos?.x != null)) {
+      console.error(nowPointsPos?.x, "nowPointsPos", nowIndex, activeIndex)
+      return
+   }
    // 移动
    smoothAnimationMove(
       {
-         x: nowPointsPos.x - 18,
+         x: nowPointsPos.x - 18, //鸟的大小
          y: nowPointsPos.y - 23
       },
-      smoothAnimationState.pointsPos,
       smoothAnimationState.pointsPos.slice(0, nowIndex)
    )
    // 当移动到屏幕最右边时候 就不进行移动了    存在移动到屏幕最右边时候  有反复的情况需要屏幕移动。所以这里注释掉了
@@ -187,7 +215,7 @@ export function moveSmoothAnimation(progress: number, activeIndex: number, isMov
  */
 function move_osmd(nowPointsPos: pointsPosType[0]) {
    // 评测移动太快看不到前面小节的分数,评测改成0.5倍速移动谱面
-   const speed = (state.modeType === 'evaluating' ? smoothAnimationState.aveSpeed * 0.5 : smoothAnimationState.aveSpeed) * (state.speed / 60)
+   const speed = (state.modeType === "evaluating" ? smoothAnimationState.aveSpeed * 0.5 : smoothAnimationState.aveSpeed) * (state.speed / 60)
    // 视口宽度
    const clientWidth = smoothAnimationState.osdmScrollDomWith
    const clientMidWidth = clientWidth / 2
@@ -258,17 +286,11 @@ export function moveTranslateXNum(translateXNum: number) {
 /**
  * 进度条和块移动方法
  */
-function smoothAnimationMove(pos: { x: number; y: number }, pointsPos: pointsPosType, progresspointsPos?: pointsPosType) {
+function smoothAnimationMove(pos: { x: number; y: number }, progresspointsPos: pointsPosType) {
    smoothAnimationState.smoothBotDom && (smoothAnimationState.smoothBotDom.style.transform = `translate(${pos.x}px, ${pos.y}px)`)
-   smoothAnimationState.canvasCtx && drawSmoothCurve(smoothAnimationState.canvasCtx, pointsPos, progresspointsPos)
-}
-/**
- * 计算视口宽度
- */
-export function calcClientWidth() {
-   smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom?.offsetWidth || 0
-   smoothAnimationState.osdmScrollDomOffsetLeft = smoothAnimationState.osdmScrollDom?.getBoundingClientRect().left || 0
+   smoothAnimationState.canvasCtx && drawSmoothCurveProgress(smoothAnimationState.canvasCtx, progresspointsPos, "#FFC121")
 }
+
 /**
  * 创建dom
  */
@@ -298,7 +320,11 @@ function createSmoothAnimation() {
    smoothAnimationState.canvasDomWith = osmdCanvasPageDom?.offsetWidth || 0
    smoothCanvasDom.width = smoothAnimationState.canvasDomWith
    smoothCanvasDom.height = smoothAnimationState.canvasDomHeight
-   smoothAnimationState.canvasCtx = smoothCanvasDom.getContext("2d")
+   const ctx = smoothCanvasDom.getContext("2d")!
+   smoothAnimationState.canvasCtx = ctx
+   ctx.imageSmoothingEnabled = true
+   ctx.lineCap = "round"
+   ctx.lineJoin = "round"
    // bot
    const smoothBotDom = document.createElement("div")
    smoothBotDom.className = "smoothBot"
@@ -313,30 +339,40 @@ function createSmoothAnimation() {
 }
 
 /**
+ * 计算视口宽度
+ */
+export function calcClientWidth() {
+   smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom?.offsetWidth || 0
+   smoothAnimationState.osdmScrollDomOffsetLeft = smoothAnimationState.osdmScrollDom?.getBoundingClientRect().left || 0
+}
+
+/**
  * 根据音符获取坐标
  */
 function getPointsPosByBatePos(): pointsPosType {
-   let totalAvInde = 0
-   // 取平均值
-   const totalAv =
-      state.times.reduce((total, item) => {
-         if (item.frequency !== -1) {
-            // -1 为休止符
-            total += item.frequency
-            totalAvInde++
-         }
-         return total
-      }, 0) / totalAvInde
-   const pointsPos = state.times.reduce((posArr: any[], item) => {
-      // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
-      if (item.bbox) {
+   // 得到音符频率数据
+   const frequencyData = state.times.map(item => {
+      return !item.frequency || item.frequency === -1 ? 0 : item.frequency
+   })
+   // 线性频率数据
+   const frequencyLineData = quantileScale(frequencyData, 8, _canvasDomHeight - 8) // 最小值和最大值
+   const pointsPos = state.times.reduce((posArr: any[], item, index) => {
+      // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox)
+      if (item.bbox?.x != null && item.noteId != null) {
          posArr.push({
+            noteId: item.noteId,
             MeasureNumberXML: item.MeasureNumberXML,
             x: item.bbox.x,
-            // 当为休止符的时候 取最下面的位置*0.9,确保能显示完整
-            y:
-               smoothAnimationState.canvasDomHeight / 2 -
-               ((((item.frequency === -1 ? 2 * totalAv * 0.1 : item.frequency) - totalAv) / totalAv) * smoothAnimationState.canvasDomHeight) / 2
+            y: _canvasDomHeight - frequencyLineData[index]
+         })
+      } else {
+         // 连续休止小节 noteId 可能为 null,所以这里取上一个音符的id
+         posArr.push({
+            // 这里当第一个音符noteId为null,找不到前一个noteId,所以兼容一下
+            noteId: item.noteId != null ? item.noteId : (posArr[posArr.length - 1]?.noteId != null ? posArr[posArr.length - 1]?.noteId : -1) + 0.01, // 这里+0.01 是制造一个假id
+            MeasureNumberXML: item.MeasureNumberXML,
+            x: item.bbox?.x != null ? item.bbox.x : posArr[posArr.length - 1]?.x || 10,
+            y: _canvasDomHeight - frequencyLineData[index]
          })
       }
       return posArr
@@ -345,156 +381,86 @@ function getPointsPosByBatePos(): pointsPosType {
    const extendPoint = {
       ...pointsPos[pointsPos.length - 1]
    }
-   extendPoint.MeasureNumberXML++
+   extendPoint.MeasureNumberXML += 100 // 防止MeasureNumberXML重复
+   extendPoint.noteId += 100 // 防止noteId重复
    // 当总长度减30小于最后一个音符时候,取最后一个音符加15
    extendPoint.x = smoothAnimationState.canvasDomWith - 30 > extendPoint.x ? smoothAnimationState.canvasDomWith - 30 : extendPoint.x + 15
    pointsPos.push(extendPoint)
    return pointsPos
 }
 
+// 数据平滑算法
+function quantileScale(data: number[], minRange = 0, maxRange = _canvasDomHeight) {
+   const sortedData = [...data].sort((a, b) => a - b)
+   return data.map(value => {
+      const rank = sortedData.indexOf(value) / (sortedData.length - 1)
+      const scaledValue = rank * (maxRange - minRange) + minRange
+      return Math.max(minRange, Math.min(scaledValue, maxRange))
+   })
+}
+
 /**
  * 使用传入的曲线的顶点坐标创建平滑曲线的顶点。
  * @param  {Array}   points  曲线顶点坐标数组,
- * @param  {Float}   tension 密集程度,默认为 0.5
- * @param  {Boolean} closed  是否创建闭合曲线,默认为 false
  * @param  {Int}     numberOfSegments 平滑曲线 2 个顶点间的线段数,默认为 20
  * @return {Array}   平滑曲线的顶点坐标数组
  */
-function createSmoothCurvePoints(pointsPos: pointsPosType, tension?: number, closed?: boolean, numberOfSegments?: number) {
-   if (pointsPos.length <= 2) {
-      return pointsPos
+function createSmoothCurvePoints(points: pointsPosType, numSegments: number) {
+   if (points.length <= 2) {
+      return points
    }
-   tension = tension ? tension : 0.5
-   closed = closed ? true : false
-   numberOfSegments = numberOfSegments ? numberOfSegments : 20
-   let ps = pointsPos.slice(0),
-      result = [],
-      x,
-      y,
-      t1x,
-      t2x,
-      t1y,
-      t2y,
-      c1,
-      c2,
-      c3,
-      c4,
-      st,
-      t,
-      i
-   if (closed) {
-      ps.unshift(pointsPos[pointsPos.length - 1])
-      ps.unshift(pointsPos[pointsPos.length - 1])
-      ps.push(pointsPos[0])
-   } else {
-      ps.unshift(pointsPos[0])
-      ps.push(pointsPos[pointsPos.length - 1])
-   }
-   for (i = 1; i < ps.length - 2; i += 1) {
-      //console.log(ps[i + 1].MeasureNumberXML, ps[i - 1].MeasureNumberXML, ps[i + 2].MeasureNumberXML, ps[i].MeasureNumberXML)
-      t1x = (ps[i + 1].x - ps[i - 1].x) * tension
-      t2x = (ps[i + 2].x - ps[i].x) * tension
-      t1y = (ps[i + 1].y - ps[i - 1].y) * tension
-      t2y = (ps[i + 2].y - ps[i].y) * tension
-      // 当中途出现反复 刚开始反复时候 53 52 22 52  (22)中途值会变小 这里强行拉大 防止算法平均值出现很大偏差
-      if (ps[i + 1].MeasureNumberXML - ps[i + 2].MeasureNumberXML > 1) {
-         const nowNumberXML = ps[i + 1].MeasureNumberXML + 1
-         //在当前值的情况下 向前一位
-         let index = ps.findIndex(item => {
-            return nowNumberXML === item.MeasureNumberXML
-         })
-         // 查询不到index时候取当前值
-         index === -1 && (index = i + 1)
-         t2x = (ps[index].x - ps[i].x) * tension
-      }
-      // 当中途出现反复 结束反复时候 22 53 22 22  (53)中途值会变大 这里强行缩小 防止算法平均值出现很大偏差
-      if (ps[i - 1].MeasureNumberXML - ps[i].MeasureNumberXML > 1) {
-         //在当前值的情况下 向后一位
-         const nowNumberXML = ps[i].MeasureNumberXML - 1
-         let index = ps.findIndex((item, index) => {
-            return nowNumberXML === item.MeasureNumberXML && nowNumberXML !== ps[index + 1]?.MeasureNumberXML
-         })
-         // 查询不到index时候取当前值
-         index === -1 && (index = i)
-         t1x = (ps[i + 1].x - ps[index].x) * tension
-      }
-      // 当中途出现跳房子 刚开始跳房子时候 35 35 54 35  (54)中途值会变大 这里强行缩小 防止算法平均值出现很大偏差
-      if (ps[i + 1].MeasureNumberXML - ps[i + 2].MeasureNumberXML < -1) {
-         const nowNumberXML = ps[i + 1].MeasureNumberXML + 1
-         //在当前值的情况下 向前一位
-         let index = ps.findIndex(item => {
-            return nowNumberXML === item.MeasureNumberXML
-         })
-         // 查询不到index时候取当前值
-         index === -1 && (index = i + 1)
-         t2x = (ps[index].x - ps[i].x) * tension
-      }
-      // 当中途出现跳房子 结束跳房子时候 54 35 54 54  (35)中途值会变小 这里强行拉大 防止算法平均值出现很大偏差
-      if (ps[i - 1].MeasureNumberXML - ps[i].MeasureNumberXML < -1) {
-         const nowNumberXML = ps[i].MeasureNumberXML - 1
-         let index = ps.findIndex((item, index) => {
-            return nowNumberXML === item.MeasureNumberXML && nowNumberXML !== ps[index + 1]?.MeasureNumberXML
-         })
-         // 查询不到index时候取当前值
-         index === -1 && (index = i)
-         t1x = (ps[i + 1].x - ps[index].x) * tension
-      }
-      const nowMeasureNumberXML = pointsPos[i - 1].MeasureNumberXML
-      const nextMeasureNumberXML = pointsPos[i].MeasureNumberXML
-      for (t = 0; t <= numberOfSegments; t++) {
-         // 小于1时候是反复   大于1是跳房子  不画曲线  停留
-         if (nextMeasureNumberXML - nowMeasureNumberXML < 0 || nextMeasureNumberXML - nowMeasureNumberXML > 1) {
-            //console.log(x, y)
-            result.push({
-               x: x as number,
-               y: y as number,
-               MeasureNumberXML: nowMeasureNumberXML
-            })
-            continue
-         }
-         st = t / numberOfSegments
-         c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1
-         c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2)
-         c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st
-         c4 = Math.pow(st, 3) - Math.pow(st, 2)
-         x = c1 * ps[i].x + c2 * ps[i + 1].x + c3 * t1x + c4 * t2x
-         y = c1 * ps[i].y + c2 * ps[i + 1].y + c3 * t1y + c4 * t2y
-         //console.log(x, y)
-         result.push({
-            x,
-            y,
-            MeasureNumberXML: t === numberOfSegments ? nextMeasureNumberXML : nowMeasureNumberXML
-         })
+   const curvePoints = []
+   for (let i = 0; i < points.length - 1; i++) {
+      const p0 = i > 0 ? points[i - 1] : points[i]
+      const p1 = points[i]
+      const p2 = points[i + 1]
+      const p3 = i !== points.length - 2 ? points[i + 2] : points[i + 1]
+      for (let j = 0; j < numSegments; j++) {
+         const t = j / numSegments
+         const t2 = t * t
+         const t3 = t2 * t
+         const x = 0.5 * (2 * p1.x + (-p0.x + p2.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3)
+         const y = 0.5 * (2 * p1.y + (-p0.y + p2.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3)
+         curvePoints.push({ x, y, MeasureNumberXML: p1.MeasureNumberXML, noteId: p1.noteId })
       }
    }
-   return result
+   return curvePoints
+}
+
+/** 初始化一条完整的旋律线dom */
+function initCanvasSmooth() {
+   const smoothDom = document.createElement("canvas")
+   smoothDom.width = smoothAnimationState.canvasDomWith
+   smoothDom.height = smoothAnimationState.canvasDomHeight
+   const smoothDomCtx = smoothDom.getContext("2d")!
+   smoothDomCtx.imageSmoothingEnabled = true
+   smoothDomCtx.lineCap = "round"
+   smoothDomCtx.lineJoin = "round"
+   // 根据坐标花线
+   drawLines(smoothDomCtx, smoothAnimationState.pointsPos, "rgba(255,255,255,0.6)")
+   smoothAnimationState.canvasSmoothDom = smoothDom
+}
+
+/**
+ * 根据进度画线
+ */
+function drawSmoothCurveProgress(context: CanvasRenderingContext2D, pointsPos: pointsPosType, color: string) {
+   context.clearRect(0, 0, smoothAnimationState.canvasDomWith, smoothAnimationState.canvasDomHeight)
+   smoothAnimationState.canvasSmoothDom && context.drawImage(smoothAnimationState.canvasSmoothDom, 0, 0)
+   drawLines(context, pointsPos, color)
 }
+
 /**
  * 根据坐标划线
  */
-function drawSmoothCurve(context: CanvasRenderingContext2D, pointsPos: pointsPosType, progresspointsPos?: pointsPosType) {
+function drawLines(context: CanvasRenderingContext2D, pointsPos: pointsPosType, color: string) {
+   if (pointsPos.length === 0) return
    context.lineWidth = 2
-   context.lineJoin = "round" // 优化锯齿
-   context.lineCap = "round" // 优化锯齿
-   context.strokeStyle = "rgba(255,255,255,0.6)"
-   drawLines(context, pointsPos)
-   if (progresspointsPos?.length) {
-      context.strokeStyle = "#FFC121"
-      drawLines(context, progresspointsPos)
-   }
-}
-function drawLines(context: CanvasRenderingContext2D, points: pointsPosType) {
+   context.strokeStyle = color
    context.beginPath()
-   context.moveTo(points[0].x, points[0].y)
-   for (let i = 1; i < points.length - 1; i++) {
-      if (Math.abs(points[i].MeasureNumberXML - points[i - 1].MeasureNumberXML) > 1) {
-         // 取消反复和跳房子连线
-         context.stroke()
-         context.beginPath()
-         context.moveTo(points[i + 1].x, points[i + 1].y)
-         continue
-      }
-      context.lineTo(points[i].x, points[i].y)
+   context.moveTo(pointsPos[0].x, pointsPos[0].y)
+   for (let i = 1; i < pointsPos.length; i++) {
+      context.lineTo(pointsPos[i].x, pointsPos[i].y)
    }
    context.stroke()
 }

文件差异内容过多而无法显示
+ 0 - 0
src/page-instrument/view-evaluat-report/component/share-top/image/audioBga.json


二进制
src/page-instrument/view-evaluat-report/component/share-top/image/videobg.png


+ 22 - 2
src/page-instrument/view-evaluat-report/component/share-top/index.module.less

@@ -160,6 +160,11 @@
     }
 }
 
+.padMiddle {
+    .cItem {
+        width: 50px;
+    }
+}
 .right {
     display: flex;
     align-items: center;
@@ -221,7 +226,7 @@
 }
 
 .playerBox {
-    width: 537px;
+    width: 536px;
     height: 314px;
     background: #FFF8F8;
     box-shadow: inset 4px -3px 6px 0px #B2E8FF;
@@ -326,7 +331,22 @@
             }
         }
     }
-
+    &.padPlayerBox{
+        width: 418px;
+        height: 248px;
+        .audioBox{
+            .audioBga1{
+                width: 112px;
+            }
+            .audioBga2{
+                width: 206px;
+            }
+            .audioVisualizer{
+                width: 288px;
+                height: 50px;
+            }
+        }
+    }
     .videoBox {
         width: 100%;
         height: 100%;

+ 67 - 33
src/page-instrument/view-evaluat-report/component/share-top/index.tsx

@@ -81,6 +81,8 @@ export default defineComponent({
       return "video";
     });
 
+    // 资源类型
+    const isPad =  navigator?.userAgent?.includes("UAWEIVRD-W09") || browserInfo?.iPad || browserInfo.isTablet;
 		const openAudioAndVideo = () => {
 			shareData.show = true;
 			if (shareData.isInitPlyr) return;
@@ -102,7 +104,7 @@ export default defineComponent({
 						shareData._plrl.on('pause', () => {
 							pauseVisualDraw()
 						});
-					}, 300); // 弹窗动画是0.25秒 这里用定时器 确保canvas 能获取到宽高
+					}, 600); // 弹窗动画是0.25秒 这里用定时器 确保canvas 能获取到宽高
 				}
 				shareData.isInitPlyr = true;
 			});
@@ -121,10 +123,10 @@ export default defineComponent({
 			canvasDom.width = width
 			canvasDom.height = height
 			// audio
-			let audioCtx : AudioContext | null = null
-			let analyser : AnalyserNode | null = null
-			let source : MediaElementAudioSourceNode | null = null
-			const dataArray = new Uint8Array(fftSize / 2)
+			// let audioCtx : AudioContext | null = null
+			// let analyser : AnalyserNode | null = null
+			// let source : MediaElementAudioSourceNode | null = null
+			// const dataArray = new Uint8Array(fftSize / 2)
 			const draw = (data: Uint8Array, ctx: CanvasRenderingContext2D, { lineGap, canvWidth, canvHeight, canvFillColor, lineColor }: propsType) => {
 				if (!ctx) return
 				const w = canvWidth
@@ -170,36 +172,53 @@ export default defineComponent({
 				ctx.fillRect(0, 0, w, h)
 			}
 			const requestAnimationFrameFun = () => {
-				requestAnimationFrame(() => {
-					analyser?.getByteFrequencyData(dataArray)
-					draw(dataArray, canvasCtx, {
-						lineGap: 2,
-						canvWidth: width,
-						canvHeight: height,
-						canvFillColor: "transparent",
-						lineColor: "rgba(255, 255, 255, 0.3)"
-					})
-					if (!isPause) {
-						requestAnimationFrameFun()
-					}
-				})
+				// requestAnimationFrame(() => {
+				// 	//analyser?.getByteFrequencyData(dataArray)
+				// 	draw(generateMixedData(48), canvasCtx, {
+				// 		lineGap: 2,
+				// 		canvWidth: width,
+				// 		canvHeight: height,
+				// 		canvFillColor: "transparent",
+				// 		lineColor: "rgba(255, 255, 255, 0.3)"
+				// 	})
+				// 	if (!isPause) {
+				// 		requestAnimationFrameFun()
+				// 	}
+				// })
+        const _time = setInterval(() => {
+          if (isPause) {
+            clearInterval(_time)
+            return
+          }
+          //analyser?.getByteFrequencyData(dataArray)
+          draw(generateMixedData(38), canvasCtx, {
+            lineGap: 3,
+            canvWidth: width,
+            canvHeight: height,
+            canvFillColor: "transparent",
+            lineColor: "rgba(255, 255, 255, 0.3)"
+          })
+        }, 300);
 			}
 			let isPause = true
 			const playVisualDraw = () => {
-				if (!audioCtx) {
-					audioCtx = new AudioContext()
-					source = audioCtx.createMediaElementSource(audioDom)
-					analyser = audioCtx.createAnalyser()
-					analyser.fftSize = fftSize
-					source?.connect(analyser)
-					analyser.connect(audioCtx.destination)
-				}
+				// if (!audioCtx) {
+				// 	audioCtx = new AudioContext()
+				// 	source = audioCtx.createMediaElementSource(audioDom)
+				// 	analyser = audioCtx.createAnalyser()
+				// 	analyser.fftSize = fftSize
+				// 	source?.connect(analyser)
+				// 	analyser.connect(audioCtx.destination)
+				// }
 				//audioCtx.resume()  // 重新更新状态   加了暂停和恢复音频音质发生了变化  所以这里取消了
 				isPause = false
 				requestAnimationFrameFun()
 			}
 			const pauseVisualDraw = () => {
 				isPause = true
+        requestAnimationFrame(()=>{
+          canvasCtx.clearRect(0, 0, width, height);
+        })
 				//audioCtx?.suspend()  // 暂停   加了暂停和恢复音频音质发生了变化  所以这里取消了
 				// source?.disconnect()
 				// analyser?.disconnect()
@@ -209,7 +228,22 @@ export default defineComponent({
 				pauseVisualDraw
 			}
 		}
-
+    function generateMixedData(size:number) {
+      const dataArray = new Uint8Array(size);
+      const baseNoiseAmplitude = 30;
+      const minFrequency = 0.01;
+      const maxFrequency = 0.2;
+      const minAmplitude = 50;
+      const maxAmplitude = 150;
+      for (let i = 0; i < size; i++) {
+          const frequency = minFrequency + Math.random() * (maxFrequency - minFrequency);
+          const amplitude = minAmplitude + Math.random() * (maxAmplitude - minAmplitude);
+          const wave = amplitude * (0.5 + 0.5 * Math.sin(frequency * i));
+          const noise = Math.floor(Math.random() * baseNoiseAmplitude) - baseNoiseAmplitude / 2;
+          dataArray[i] = Math.min(255, Math.max(0, Math.floor(wave + noise)));
+      }
+      return dataArray;
+    }
     return () => (
       <>
         <div class={[styles.headerTop, browserInfo.android && styles.android]}>
@@ -221,14 +255,14 @@ export default defineComponent({
               {/* <div class={styles.lcName}>{state.examSongName}</div> */}
               <Title class={styles.lcName} text={state.examSongName} rightView={false} />
               <div class={styles.lcScore}>
-                {level[scoreData.value.heardLevel]}|速度:{Math.floor(scoreData.value.speed)}|综合分数:{scoreData.value.score}分
+                {level[scoreData.value.heardLevel]}|速度:{Math.floor(scoreData.value.speed)}|综合分数:{scoreData.value.score}分
               </div>
             </div>
           </div>
 
           {/* 音准、节奏、完整度纬度 */}
 
-          <div class={styles.middle}>
+          <div class={[styles.middle, isPad && styles.padMiddle]}>
             {state.isPercussion ? null : (
               <div onClick={() => handleChange("intonation")} class={[styles.cItem, "evaluting-report-1", itemType.value === "intonation" && styles.active]}>
                 <span class={styles.mScore}>{scoreData.value.intonation}分</span>
@@ -431,13 +465,13 @@ export default defineComponent({
               shareData._plrl?.pause();
             }}
           >
-            <div class={styles.playerBox}>
+            <div class={[styles.playerBox, isPad && styles.padPlayerBox]}>
               {mediaType.value === "audio" ? (
                 <div class={styles.audioBox}>
                   <canvas class={styles.audioVisualizer} id="audioVisualizer"></canvas>
-                  <Vue3Lottie class={styles.audioBga} animationData={audioBga} autoPlay={false} loop={true}></Vue3Lottie>
-                  <Vue3Lottie class={styles.audioBga1} animationData={audioBga1} autoPlay={false} loop={true}></Vue3Lottie>
-                  <Vue3Lottie class={styles.audioBga2} animationData={audioBga2} autoPlay={false} loop={true}></Vue3Lottie>
+                  <Vue3Lottie class={styles.audioBga} animationData={audioBga} autoPlay={true} loop={true}></Vue3Lottie>
+                  <Vue3Lottie class={styles.audioBga1} animationData={audioBga1} autoPlay={true} loop={true}></Vue3Lottie>
+                  <Vue3Lottie class={styles.audioBga2} animationData={audioBga2} autoPlay={true} loop={true}></Vue3Lottie>
                   <audio crossorigin="anonymous" id="audioSrc" src={scoreData.value.videoFilePath} controls="false" preload="metadata" playsinline />
                 </div>
               ) : (

+ 0 - 2
src/page-instrument/view-evaluat-report/index.module.less

@@ -190,14 +190,12 @@
 
 .bg2Left {
   width: 52px;
-  height: 125px;
   position: absolute;
   left: 0;
   top: 0;
 }
 .bg2Right {
   width: 52px;
-  height: 125px;
   position: absolute;
   right: 0;
   top: 0;

+ 36 - 19
src/state.ts

@@ -18,7 +18,7 @@ import { changeSongSourceByBate } from "/src/view/audio-list"
 import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTime, moveTranslateXNum, destroySmoothAnimation, calcClientWidth } 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"
+import { musicScoreRef, headerColumnHide } from "/src/page-instrument/view-detail/index"
 import { headTopData } from "/src/page-instrument/header-top/index";
 
 const query: any = getQuery();
@@ -637,11 +637,18 @@ export const onEnded = () => {
 
 // 根据当前小节动态设置,右上角展示的速度
 const dynamicShowPlaySpeed = (index: number) => {
-  const item: any = state.times[index];
-  if (item && item.measureSpeed ) {
-    // console.log('速度1',item.measureSpeed)
-    state.speed = Math.floor(state.basePlayRate * item.measureSpeed)
+  if (!headerColumnHide.value) {
+    console.log('动态计算速度')
+    const item: any = state.times[index];
+    if (item && item.measureSpeed ) {
+      // console.log('速度1',item.measureSpeed)
+      const newSpeed = Math.floor(state.basePlayRate * item.measureSpeed)
+      if (state.speed !== newSpeed) {
+        state.speed = newSpeed;
+      }
+    }
   }
+
 }
 
 // 开始播放时,计算mp3的播放倍率
@@ -779,10 +786,10 @@ export const skipNotePlay = async (itemIndex: number, isStart = false) => {
  * 切换曲谱播放状态
  * @param playState 需要切换的状态 play:播放, paused: 暂停
  */
-export const togglePlay = async (playState: "play" | "paused", sourceType?: string) => {
+export const togglePlay = async (playState: "play" | "paused", isForceCLoseToast?:boolean) => {
   // 如果mp3资源还在加载中,给出提示
   if (!state.isAppPlay && !state.audioDone) {
-    if (sourceType !== 'courseware') showToast('音频资源加载中,请稍后')
+    if (!isForceCLoseToast) showToast('音频资源加载中,请稍后')
     return
   }
   // 播放之前  当为评测模式和不为MIDI时候按  是否禁用节拍器  切换音源
@@ -1293,7 +1300,7 @@ export const handleRessetState = () => {
   if (state.modeType === "evaluating") {
     handleStartEvaluat();
   } else if (state.modeType === "practise") {
-    togglePlay("paused");
+    togglePlay("paused", true);
   } else if (state.modeType === "follow") {
     toggleFollow(false);
   }
@@ -1332,10 +1339,11 @@ const getMusicInfo = async (res: any) => {
   downloadXmlStr.value = xmlString //给musice-score 赋值xmlString 以免加载2次
   const tracks = xmlToTracks(xmlString) //获取声轨列表
   // 设置音源  track 为当前的声轨 index为当前的
-  const { track, index } = state.isSimplePage ? { track:tracks[0], index:0} : initMusicSource(res.data, tracks, partIndex)
+  const { track, index, musicalInstrumentId } = state.isSimplePage ? { track:tracks[0], index:0, musicalInstrumentId: '' } : initMusicSource(res.data, tracks, partIndex)
+  const realTrack = musicalInstrumentId && res.data?.musicalInstruments?.length ? res.data?.musicalInstruments.find((item: any) => item?.id == musicalInstrumentId)?.code?.split(',')?.[0] : '';
   const musicInfo = {
     ...res.data,
-    track
+    track: res.data.musicSheetType === 'CONCERT' ? track : realTrack
   };
   console.log("🚀 ~ musicInfo:", musicInfo);
   setState(musicInfo, index);
@@ -1354,8 +1362,8 @@ function xmlToTracks(xmlString: string) {
 }
 // 设置音源
 function initMusicSource(data: any, tracks: string[], partIndex: number) {
-  let track:string,index:number
-  const { instrumentId } = storeData.user
+  let track:string,index:number, musicalInstrumentId: string
+  const instrumentId = query.instrumentId || storeData.user?.instrumentId
   let { musicSheetType, isAllSubject, musicSheetSoundList, musicSheetAccompanimentList } = data
   musicSheetSoundList || (musicSheetSoundList = [])
   musicSheetAccompanimentList || (musicSheetAccompanimentList = [])
@@ -1383,6 +1391,7 @@ function initMusicSource(data: any, tracks: string[], partIndex: number) {
     index = tracks.findIndex(item => {
       return item === track
     })
+    musicalInstrumentId = musicObj?.musicalInstrumentId
   } else {
     /* 合奏 */
     // 支持总谱 并且当前是总谱。partIndex是999时候,或者默认是总谱并且partIndex为-1时候  -1就是partIndex没有值
@@ -1390,14 +1399,16 @@ function initMusicSource(data: any, tracks: string[], partIndex: number) {
         // 总谱渲染
         state.isCombineRender = true
         state.partListNames = tracks
-        banSongObj = musicSheetAccompanimentList.find((item: any) => {
+        // 总谱演唱模式是 范唱
+        fanSongObj = musicSheetAccompanimentList.find((item: any) => {
           return item.audioPlayType === "SING"
         })
         // 先取scoreAudioFileUrl的值
-        if(banSongObj?.scoreAudioFileUrl){
-          banSongObj.audioFileUrl = banSongObj.scoreAudioFileUrl
-          banSongObj.audioBeatMixUrl = banSongObj.scoreAudioBeatMixUrl
+        if(fanSongObj?.scoreAudioFileUrl){
+          fanSongObj.audioFileUrl = fanSongObj.scoreAudioFileUrl
+          fanSongObj.audioBeatMixUrl = fanSongObj.scoreAudioBeatMixUrl
         }
+        // 总谱演奏模式是 伴奏
         accompanyObj = musicSheetAccompanimentList.find((item: any) => {
           return item.audioPlayType === "PLAY"
         })
@@ -1408,6 +1419,7 @@ function initMusicSource(data: any, tracks: string[], partIndex: number) {
         }
         track = "总谱"
         index = 999
+        musicalInstrumentId = ''
     }else{
       // 合奏只显示一个声轨
       track = tracks[partIndex===-1?0:partIndex]
@@ -1427,6 +1439,7 @@ function initMusicSource(data: any, tracks: string[], partIndex: number) {
       index = tracks.findIndex(item => {
         return item === track
       })
+      musicalInstrumentId = musicObj?.musicalInstrumentId
     }
   }
   // 当没有任何曲目的时候报错
@@ -1471,7 +1484,8 @@ function initMusicSource(data: any, tracks: string[], partIndex: number) {
   }
   return {
     index,
-    track
+    track,
+    musicalInstrumentId
   }
 }
 const setState = (data: any, index: number) => {
@@ -1587,7 +1601,8 @@ const setState = (data: any, index: number) => {
    * 各平台的乐器声部id不统一,为了兼容处理老的数据,加上乐器code码,此码唯一
    * 获取指法code
    */
-  const code = state.isConcert ? matchVoicePart(state.trackId, "CONCERT") : matchVoicePart(state.musicalCodeId, "SINGLE");
+  // const code = state.isConcert ? matchVoicePart(state.trackId, "CONCERT") : matchVoicePart(state.musicalCodeId, "SINGLE");
+  const code = matchVoicePart(state.trackId, "CONCERT")
   state.fingeringInfo = subjectFingering(code);
   console.log("🚀 ~ state.fingeringInfo:", code, state.fingeringInfo, state.trackId, state.track);
   state.musicalCodeId = state.fingeringInfo?.id || 0
@@ -1663,6 +1678,8 @@ const setState = (data: any, index: number) => {
   } else {
     state.setting.frequency = state.setting.frequency || state.baseFrequency
   }
+  state.playBtnDirection = query.imagePos === 'left' ? 'left' : 'right';
+  state.isAttendClass = (query.imagePos === 'left' || query.imagePos === 'right') ? true : false;
 };
 
 // 多分轨合并显示标示
@@ -1867,7 +1884,7 @@ watch(
     state.vfmeasures.forEach((item: any, idx: number) => {
       const measureNum = item.getAttribute('data-num') ? Number(item.getAttribute('data-num')) : -1;
       const nextMeasureNum = state.vfmeasures[idx+1]?.getAttribute('data-num') ? Number(state.vfmeasures[idx+1]?.getAttribute('data-num')) : -1;
-      if (measureNum >= 0 && (measureNum === state.activeMeasureIndex || (measureNum < state.activeMeasureIndex && nextMeasureNum > state.activeMeasureIndex)) ) {
+      if (measureNum >= 0 && (measureNum === state.activeMeasureIndex || (measureNum < state.activeMeasureIndex && nextMeasureNum > state.activeMeasureIndex)) || (measureNum < state.activeMeasureIndex && nextMeasureNum == -1) ) {
         item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#132D4C")
         item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#040D1E")
         // 预备小节

+ 5 - 0
src/style.css

@@ -195,3 +195,8 @@ body {
 html {
   font-size: 64px;
 }
+
+@font-face {
+  font-family: "DIN-Bold";
+  src: url("./assets/DIN_Alternate_Bold.ttf");
+}

+ 32 - 0
src/utils/index.ts

@@ -4,6 +4,33 @@ import { getQuery } from "./queryString";
 /** 获取浏览器信息 */
 export const browser = () => {
 	const u = navigator.userAgent;
+	const isAndroid = /(?:Android)/.test(u);
+  	const isFireFox = /(?:Firefox)/.test(u);
+	function isIpadFun() {
+	const ua = window.navigator.userAgent;
+	let IsIPad = false;
+	if (/ipad/i.test(ua)) {
+		IsIPad = true;
+	}
+	// iPad from IOS13
+	const macApp = ua.match(/Macintosh/i) != null;
+	if (macApp) {
+		// need to distinguish between Macbook and iPad
+		const canvas = document.createElement('canvas');
+		if (canvas != null) {
+		const context: any =
+			canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
+		if (context) {
+			const info = context.getExtension('WEBGL_debug_renderer_info');
+			if (info) {
+			const renderer = context.getParameter(info.UNMASKED_RENDERER_WEBGL);
+			if (renderer.indexOf('Apple') != -1) IsIPad = true;
+			}
+		}
+		}
+	}
+	return IsIPad;
+	}	
 	return {
 		trident: u.indexOf("Trident") > -1, //IE内核
 		presto: u.indexOf("Presto") > -1, //opera内核
@@ -24,6 +51,11 @@ export const browser = () => {
 		isStudent: u.indexOf("ORCHESTRASTUDENT") > -1 || u.includes("COLEXIUSTUDENT"),
 		isSchool: u.indexOf("ORCHESTRASCHOOL") > -1,
 		iPad: u.indexOf("iPad") > -1, //是否iPad
+		isTablet:
+		/(?:iPad|PlayBook)/.test(u) ||
+		(isAndroid && !/(?:Mobile)/.test(u)) ||
+		(isFireFox && /(?:Tablet)/.test(u)) ||
+		isIpadFun(),
 		webApp: u.indexOf("Safari") == -1, //是否web应该程序,没有头部与底部
 		weixin: u.indexOf("MicroMessenger") > -1, //是否微信 (2015-01-22新增)
 		alipay: u.indexOf("AlipayClient") > -1, //是否支付宝

+ 6 - 5
src/view/audio-list/index.tsx

@@ -86,8 +86,8 @@ export const setAudioPlaybackRate = (rate: number) => {
 
 /** 获取当前播放的时间 */
 export const getAudioCurrentTime = () => {
-	// 如果是midi播放
-	if (state.isAppPlay) {
+	// 如果是midi播放,或者是评测
+	if (state.isAppPlay || state.modeType === 'evaluating') {
 		// const c = getMidiCurrentTime();
 		return audioData.progress;
 	}
@@ -269,14 +269,15 @@ export default defineComponent({
 			const time = currentTime / 1000;
 			audioData.progress = time;
 			tickAnimate(time);
-			audioData.songEle && (audioData.songEle.currentTime = time);
-			audioData.backgroundEle && (audioData.backgroundEle.currentTime = time);
-			audioData.mingSongEle && (audioData.mingSongEle.currentTime = time);
+			// audioData.songEle && (audioData.songEle.currentTime = time);
+			// audioData.backgroundEle && (audioData.backgroundEle.currentTime = time);
+			// audioData.mingSongEle && (audioData.mingSongEle.currentTime = time);
 			audioData.duration = total / 1000;
 			if (
 				res?.content?.totalDuration > 1000 &&
 				currentTime >= total
 			) {
+				console.log('播放结束1111',evaluatingData.isAudioPlayEnd,currentTime,total)
 				if (evaluatingData.isAudioPlayEnd) return
 				evaluatingData.isAudioPlayEnd = true
 				onEnded();

+ 15 - 8
src/view/evaluating/index.tsx

@@ -114,6 +114,8 @@ export const evaluatingData = reactive({
   needReplayEvaluat: false, // 手动取消评测,需要自动开始评测
   needPlayTick: false, // 评测时,mp3节拍器需要等待音频开始播放后再执行播放节拍器的圆点动画
   tipErjiShow: false, // 评测提示弹窗
+  onceErjiPopShow: false, // 是否已经提示过耳机弹窗,重新进入评测页面,重置该状态为false,手动关掉耳机弹窗,改变该状态为true,本次评测都不在提示耳机状态弹窗
+  needCheckErjiStatus: true, // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
 });
 
 const sendOffsetTime = async (offsetTime: number) => {
@@ -409,19 +411,22 @@ export const handleStartBegin = async (preTimes?: number) => {
 		evaluatingData.isBeginMask = false
 		onPlay();
 	}
-	if (evaluatingData.isErrorState) return
+	if (evaluatingData.isErrorState) {
+    state.playState = 'paused';
+    evaluatingData.startBegin = false;
+    return
+  }
 	//开始录音
 	// await api_startRecording({
 	// 	accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
 	// 	firstNoteTime: preTimes || 0,
 	// });
-	let rate = state.speed / state.originSpeed;
-	rate = parseFloat(rate.toFixed(2));
+  const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
 	await api_startRecordingCb({
 		// accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
 		accompanimentState: !state.accompany ? 0 : 1, // 评测没有伴奏时,静音播放
 		firstNoteTime: preTimes || 0,
-		speedRate: rate, // 播放倍率
+    speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
 	}, () => {
 		if (state.isAppPlay) {
 			setTimeout(() => {
@@ -627,6 +632,7 @@ export const handleViewReport = (key: "recordId" | "recordIdStr", type: "gym" |
     statusBarTextColor: false,
     isOpenLight: true,
     c_orientation: 0,
+    showLoadingAnim: true
   });
 };
 
@@ -644,6 +650,7 @@ const handleAccompanyError = (res?: IPostMessage) => {
   console.log("异常信息返回", res);
   if (res?.content) {
     const { type, reson } = res.content;
+    state.playState = 'paused'
     switch (type) {
       case "enterBackground":
       // App退到后台
@@ -729,8 +736,8 @@ export default defineComponent({
     /** 记录状态 */
     const hanlde_record = () => {
       // 取消指法
-      record_old_data.finger = state.setting.displayFingering;
-      state.setting.displayFingering = false;
+      // record_old_data.finger = state.setting.displayFingering;
+      // state.setting.displayFingering = false;
       // 切换为伴奏
       record_old_data.play_mode = state.playSource;
       record_old_data.enableAccompaniment = state.setting.enableAccompaniment;
@@ -743,7 +750,7 @@ export default defineComponent({
     /** 还原状态 */
     const handle_reduction = () => {
       // 还原指法
-      state.setting.displayFingering = record_old_data.finger;
+      // state.setting.displayFingering = record_old_data.finger;
       state.playSource = record_old_data.play_mode;
 
       // 如果关闭伴奏, 结束评测取消静音
@@ -785,7 +792,7 @@ export default defineComponent({
       // evaluatingData.resulstMode = true;
       // evaluatingData.resultData = {...getLeveByScore(10), score: 10, intonation: 10, cadence: 30, integrity: 40}
       // console.log("🚀 ~ evaluatingData.resultData:", evaluatingData.resultData)
-
+      evaluatingData.onceErjiPopShow = false;
       evaluatingData.evaluatings = {};
       evaluatingData.soundEffectFrequency = 0;
       evaluatingData.checkStep = 0;

+ 2 - 1
src/view/fingering/fingering-config.ts

@@ -338,7 +338,7 @@ export const matchVoicePart = (id: number | string, type: "SINGLE" | "CONCERT"):
       code = code.toLocaleLowerCase().replace(/ /g, "");
       for (let sKey in subject) {
         let pitchKey = sKey;
-        if (typeof sKey === "string") {
+        if (typeof sKey === "string" && isNaN(Number(sKey)) ) {
           pitchKey = pitchKey.toLocaleLowerCase().replace(/ /g, "");
           pitchKey = pitchKey.replace(/[_0-9]+$/, '');
         }
@@ -505,6 +505,7 @@ export const subjectFingering = (subjectId: number | string): IFingering => {
         hasTizhi: false,
         id: 39,
       };
+    case 137: // 口风琴
     case "melodica": // 口风琴
       return {
         name: "melodica",

+ 10 - 2
src/view/fingering/index.module.less

@@ -5,9 +5,17 @@
   justify-content: space-evenly;
   align-items: center;
   padding: 0 10px 4px 10px;
-  overflow: hidden;
+  position: relative;
+}
+.emptyDom {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background: transparent;
+  z-index: 999;
 }
-
 .vertical {
   position: relative;
   padding: 10px 0 10px 0;

+ 10 - 1
src/view/fingering/index.tsx

@@ -2,11 +2,13 @@ import { computed, defineComponent, onBeforeMount, reactive } from "vue";
 import styles from "./index.module.less";
 import state from "/src/state";
 import { getFingeringConfig, ITypeFingering } from "./fingering-config";
+import { evaluatingData } from "/src/view/evaluating";
+import { followData } from "/src/view/follow-practice/index"
 
 export default defineComponent({
   name: "fingering",
   emits: ["open"],
-  setup(props, { emit }) {
+  setup(props, { emit, expose }) {
     const fingerData = reactive({
       relationshipIndex: 0,
       subject: null as unknown as ITypeFingering,
@@ -34,6 +36,10 @@ export default defineComponent({
     });
 
     const doubeClick = () => {
+      // 如果在评测和跟练中,双击指法不跳转
+      if ((state.modeType === 'evaluating' && evaluatingData.startBegin) || (state.modeType === 'follow' && followData.start)) {
+        return;
+      }
       const nowTime = Date.now();
       if (nowTime - fingerData.delay < 300) {
         emit("open");
@@ -41,6 +47,9 @@ export default defineComponent({
       }
       fingerData.delay = nowTime;
     };
+    expose({
+      doubeClick
+    })
     return () => {
       // console.log("音高", realKey.value);
       const relationship = fingerData.subject?.relationship?.[realKey.value] || [];

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

@@ -7,7 +7,7 @@ import Selection from "../selection";
 import styles from "./index.module.less";
 import queryString from "query-string";
 import { getGradualLengthByXml } from "/src/helpers/calcSpeed";
-import { resetFormate, resetGivenFormate, setGlobalMusicSheet } from "/src/helpers/customMusicScore"
+import { resetFormate, resetGivenFormate, setGlobalMusicSheet, limitSingleSvgPageHeight } from "/src/helpers/customMusicScore"
 import { setGlobalData } from "/src/utils";
 import Loading from "/src/view/audio-list/loading"
 import { storeData } from "/src/store";
@@ -104,6 +104,7 @@ export default defineComponent({
 				// drawMetronomeMarks: false,
 				// ...this.opotions,
 				colorStemsLikeNoteheads: true, // 是否将音符柄的颜色设置为与它们的音符头相同,默认false
+				// drawingParameters: "compact" // 使用紧凑布局
 			});
 			// osmd.EngravingRules.CompactMode = true // 紧凑模式
 			// osmd.EngravingRules.PageRightMargin = state.isSingleLine ? (window.innerWidth+200)/10 : 2;
@@ -124,7 +125,7 @@ export default defineComponent({
 				osmd.EngravingRules.PageBottomMargin = 0;
 			} else {
 				// osmd.EngravingRules.PageTopMargin = state.isEvaluatReport ? 7 : 3; // 顶部间距
-				osmd.EngravingRules.PageTopMargin = 3;
+				osmd.EngravingRules.PageTopMargin = state.isPreView ? 1 : 3;
 				osmd.EngravingRules.PageTopMarginNarrow = 3;
 				osmd.EngravingRules.PageLeftMargin = 3.6;
 				osmd.EngravingRules.PageRightMargin = 3;

+ 10 - 7
src/view/plugins/toggleMusicSheet/choosePartName/index.tsx

@@ -1,4 +1,4 @@
-import { PropType, computed, defineComponent, ref, toRefs, onMounted, watch } from 'vue'
+import { PropType, computed, defineComponent, ref, toRefs, onMounted, watch, nextTick } from 'vue'
 import { Picker, Button, Icon } from 'vant'
 import styles from './index.module.less'
 import state, { IPlatform } from "/src/state";
@@ -76,12 +76,15 @@ export default defineComponent({
               }}
             />
             <div class={styles.button} onClick={() => {
-                // console.log(1111,selectIndex.value)
-                if (partIndexChanged.value) {
-                  emit('close', selectIndex.value)
-                } else {
-                  emit('close', partIndex.value)
-                }
+                myPicker.value.confirm()
+                nextTick(()=>{
+                  // console.log(1111,selectIndex.value)
+                  if (partIndexChanged.value) {
+                    emit('close', selectIndex.value)
+                  } else {
+                    emit('close', partIndex.value)
+                  }
+                })
               }
             }></div>
           </div>

+ 1 - 0
src/view/plugins/useDrag/dragbom.tsx

@@ -33,6 +33,7 @@ export default defineComponent({
       }
     }
     onMounted(() => {
+      console.log('拖动11')
       nextTick(() => {
         setTimeout(() => {
           initGuidePos();

+ 14 - 12
src/view/selection/index.module.less

@@ -83,8 +83,8 @@
 
 .scoreItem {
     position: absolute;
-    left: 50%;
-    top: -90%;
+    left: 80%;
+    top: -120%;
     transform: translateX(-50%);
     font-size: 16px;
     font-family: "Roboto", sans-serif;
@@ -161,7 +161,7 @@
 .followTipUp, .followTipDown {
     display: flex;
     align-items: center;
-    background: rgba(0,0,0,0.6);
+    background: rgba(0,0,0,0.75);
     position: relative;
     padding: 6px 10px;
     border-radius: 16px;
@@ -186,7 +186,7 @@
         content: "";
         position: absolute;
         left: 50%;
-        bottom: -8PX;
+        bottom: -9PX;
         transform: translateX(-50%);
         width: 13Px;
         height: 9Px;
@@ -298,17 +298,19 @@
     // background: #07c160;
 }
 
-.staveBg {
-    &::before {
-        content: "";
+// 阴影
+.staveBgContainer{
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    z-index: -100;
+    .staveBg{
         position: absolute;
-        left: 0;
-        bottom: -3Px;
-        width: 100%;
-        height: 8Px;
+        height: 8px !important;
         background: linear-gradient(rgba(7, 24, 56, 0.5) 0%, #010D31 100%);
-        z-index: 0;
         filter: blur(5Px);
         opacity: 0.7;
+        margin-top: -4px;
     }
 }

+ 39 - 12
src/view/selection/index.tsx

@@ -145,19 +145,46 @@ const calcNoteData = () => {
 				MeasureNumberXMLList.push(item.MeasureNumberXML);
 			} else {
 				if (item.multipleRestMeasures) {
-					const preItem = selectData.staves.find(
-						(n: any) => n.MeasureNumberXML === item.MeasureNumberXML - 1
-					);
-					if (preItem?.staveBox) {
-						noteItem.staveBox = {
-							left: preItem.staveBox.left,
-							top: preItem.staveBox.top,
-							width: preItem.staveBox.width,
-							// height: preItem.staveBox.height,
-						};
-						selectData.staves.push(noteItem);
-						MeasureNumberXMLList.push(item.MeasureNumberXML);
+					if (state.isCombineRender) {
+						let currentItem = null;
+						for (let index = 0; index < state.vfmeasures.length; index++) {
+							const element = state.vfmeasures[index];
+							const measureNum = element.getAttribute('data-num') ? Number(element.getAttribute('data-num')) : -1;
+							const nextMeasureNum = state.vfmeasures[index+1]?.getAttribute('data-num') ? Number(state.vfmeasures[index+1]?.getAttribute('data-num')) : -1;
+							if (measureNum === item.MeasureNumberXML || item.MeasureNumberXML < nextMeasureNum || nextMeasureNum == -1) {
+								currentItem = element
+								break;
+							}
+						}
+						const staveBbox = currentItem?.querySelector('.vf-stave')?.getBoundingClientRect() || { x: 0, width: 0, y: 0, height: 0 };
+						if (currentItem) {
+							noteItem.staveBox = {
+								left: staveBbox.x - parentLeft + "px",
+								// top: ((item.stave.y || 0) - 5) * state.zoom + "px",
+								top: staveBbox.y - parentTop  + "px",
+								width: staveBbox.width + "px",
+								height: staveBbox.height + "px",
+								// height: preItem.staveBox.height,
+							};
+							selectData.staves.push(noteItem);
+							MeasureNumberXMLList.push(item.MeasureNumberXML);
+						}
+					} else {
+						const preItem = selectData.staves.find(
+							(n: any) => n.MeasureNumberXML === item.MeasureNumberXML - 1
+						);
+						if (preItem?.staveBox) {
+							noteItem.staveBox = {
+								left: preItem.staveBox.left,
+								top: preItem.staveBox.top,
+								width: preItem.staveBox.width,
+								// height: preItem.staveBox.height,
+							};
+							selectData.staves.push(noteItem);
+							MeasureNumberXMLList.push(item.MeasureNumberXML);
+						}
 					}
+
 				}
 			}
 		}

+ 1 - 1
vite.config.ts

@@ -76,7 +76,7 @@ export default defineConfig({
         // target: "https://kt.colexiu.com",
         // target: "https://test.lexiaoya.cn",
         // target: "https://kt.colexiu.com",
-        //target: "https://dev.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
+        // target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
         target: "https://test.kt.colexiu.com",
         //target: "https://mec.colexiu.com",
         changeOrigin: true,

部分文件因为文件数量过多而无法显示