Bladeren bron

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

TIANYONG 10 maanden geleden
bovenliggende
commit
e8020664e3

+ 23 - 7
src/helpers/formateMusic.ts

@@ -623,7 +623,30 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 	if (!xml) return "";
 	
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+
 	const measures = Array.from(xmlParse.getElementsByTagName("measure"));
+	const minutes: any = xmlParse.getElementsByTagName("per-minute");
+	let speeds: any = []
+	for (const minute of minutes) {
+		if (minute.textContent && !!Number(minute.textContent)) {
+			speeds.push(Number(minute.textContent))
+		}
+	}
+	speeds = [...new Set(speeds)]
+	const hasVaryingSpeed = speeds.length > 1 ? true : false
+  // 如果后台没有设置速度,默认取xml速度,如果xml也没有速度,默认赋值100
+	if (state.originSpeed === 0) {
+		state.originSpeed = speeds[0] ? speeds[0] : 100;
+		state.speed = state.originSpeed;
+	}
+	// 如果谱面和小节都没有打速度,osmd设置的小节速度默认取后台设置的速度
+	if (speeds.length === 0) {
+		;(window as any).baseMeasureSpeed = state.originSpeed
+	} else {
+		state.originAudioPlayRate = speeds[0] / state.originSpeed
+	}
+	console.log('是否是变速的曲子:',hasVaryingSpeed,speeds)
+
 	const repeats: any = Array.from(xmlParse.querySelectorAll('repeat'));
 	compatibleXmlPitchVoice(xmlParse);
 	// 获取作词、作曲家
@@ -705,13 +728,6 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
         </note>`;
 		}
 	}
-	// 如果曲谱详情接口没有返回速度,则取xml第一小节的速度,如果取不到,则取默认速度:100
-	if (!speed || speed == -1) {
-		speed = 100
-	}
-	if (!state.originSpeed) {
-		state.originSpeed = state.speed = speed || 100
-	}
 	return new XMLSerializer().serializeToString(xmlParse);
 };
 

+ 1 - 1
src/helpers/metronome.ts

@@ -199,7 +199,7 @@ class Metronome {
 				return
 			}			
 		}
-		console.log("播放自带的节拍器 233333")
+		// console.log("播放自带的节拍器 233333")
 		if (!metronomeData.initPlayerState || state.playState === 'paused') return;
 		const beatVolume = state.setting.beatVolume / 100
 		// this.source = metronomeData.activeMetro?.index === 0 ? this.source1 : this.source2;

+ 1 - 1
src/page-instrument/component/the-music-list/index.tsx

@@ -9,7 +9,7 @@ import { getQuery } from "/src/utils/queryString";
 
 const query: any = getQuery();
 export const isMusicList = computed(()=>{
-	return !(state.playState == "play" || followData.start || evaluatingData.startBegin || query.workRecord || query.modelType || state.platform === IPlatform.PC || query.isCbs)
+	return !(query.workRecord || query.modelType || state.platform === IPlatform.PC || query.isCbs)
 })
 export const musicListShow = ref(false)
 export default defineComponent({

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

@@ -1,4 +1,30 @@
 .recommendation{
+    &.follow{
+        .head{
+            background: url("/src/page-instrument/header-top/image/headImg1.png") no-repeat;
+            background-size: 100% 100%; 
+        }
+        .content{
+            background: #ACDDEA;
+            box-shadow: 0px 4px 0px 0px #5EA2B9;
+            .conBox{
+                background: #E3F3F5;
+            }
+        }
+    }
+    &.evaluating{
+        .head{
+            background: url("/src/page-instrument/header-top/image/headImg2.png") no-repeat;
+            background-size: 100% 100%; 
+        }
+        .content{
+            background: #B0CDFF;
+            box-shadow: 0px 4px 0px 0px #759CE4;
+            .conBox{
+                background: #EAF1FB;
+            }
+        }
+    }
     .head{
         background: url("/src/page-instrument/header-top/image/headImg.png") no-repeat;
         background-size: 100% 100%;

+ 1 - 1
src/page-instrument/custom-plugins/helper-model/recommendation/index.tsx

@@ -106,7 +106,7 @@ export default defineComponent({
 			getTypeList();
 		});
 		return () => (
-			<div class={[styles.recommendation]}>
+			<div class={[styles.recommendation,styles[state.modeType]]}>
 				<div class={styles.head}>
 					<img class={styles.headTit} src={headImg("recommendationName.png")} />
 					<img class={styles.closeImg} src={headImg("closeImg.png")} onClick={()=>{ emit("close") }} />

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

@@ -289,6 +289,7 @@ export default defineComponent({
             speedRate: rate, // 播放倍率
             musicRenderType: state.musicRenderType,
             musicSheetId: state.examSongId,
+            'part-index': state.partIndex
           });
           return;
         }

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


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


+ 2 - 1
src/page-instrument/header-top/index.module.less

@@ -1,9 +1,10 @@
 .headerTop {
     display: flex;
     align-items: center;
-    width: 100%;
+    width: 100vw;
     height: 100%;
     flex-shrink: 0;
+    margin-left: calc(-1 * var(--detailDataPaddingLeft));
     padding: 0 30px;
     background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
 }

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

@@ -76,10 +76,11 @@ export const headTopData = reactive({
         state.isSingleLine = true;
         refreshMusicSvg();
       }
+      smoothAnimationState.isShow.value = false; // 隐藏旋律线
       state.playIngSpeed = state.originSpeed;
       handleStartEvaluat();
       // 开发模式,把此处打开
-      state.modeType = "evaluating";
+      // state.modeType = "evaluating"
       // evaluatingData.rendered = true;
       // evaluatingData.soundEffectMode = true;
     } else if (value === "follow") {
@@ -329,7 +330,7 @@ export default defineComponent({
     /** 选段按钮 */
     const selectBtn = computed(() => {
       // 选择模式 不显示
-      if (headTopData.modeType !== "show" || ["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
+      if (headTopData.modeType !== "show" || ["follow"].includes(state.modeType)) return { display: false, disabled: true };
       // 音频播放中 禁用
       if (state.playState === "play") return { display: true, disabled: true };
 
@@ -399,8 +400,8 @@ export default defineComponent({
     const toggleBtn = computed(() => {
       // 选择模式, url设置模式 不显示
       if (headTopData.modeType !== "show" || !headTopData.showBack) return { display: false, disabled: false };
-      // 跟练开始, 评测开始 禁用
-      if (followData.start || evaluatingData.startBegin) return { display: true, disabled: true };
+      // 跟练开始, 评测开始 播放开始 隐藏
+      if (state.playState == "play" || followData.start || evaluatingData.startBegin) return { display: false, disabled: false };
 
       return {
         display: true,
@@ -608,6 +609,34 @@ export default defineComponent({
             {state.modeType === "practise" && smoothAnimationState.isShow.value ? (
               <div
                 class={[styles.title, "driver-8", isMusicList.value && styles.isMusicList]}
+          {
+            !(state.playState == "play" || followData.start || evaluatingData.startBegin) &&
+              <div class={styles.headTopLeftBox}>
+                <img src={iconBack} class={['headTopBackBtn', styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
+                {
+                  smoothAnimationState.isShow.value ?
+                    <div class={[styles.title,isMusicList.value && styles.isMusicList]} onClick={()=>{
+                        isMusicList.value && (musicListShow.value = true)
+                      }}>
+                        <NoticeBar
+                          text={state.examSongName}
+                          background="none"
+                        />
+                    </div> :
+                    isMusicList.value &&
+                    <img src={listImg} class={[styles.img]} onClick={()=>{
+                      musicListShow.value = true
+                    }} />
+                }
+              </div>
+          }
+          {/* 模式切换 */}
+            { 
+            state.playType === "play" &&
+              <div 
+                id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
+                style={{ display: toggleBtn.value.display ? "" : "none" }}
+                class={[styles.modeChangeBox, toggleBtn.value.disabled && styles.disabled]} 
                 onClick={() => {
                   isMusicList.value && (musicListShow.value = true);
                 }}

+ 79 - 15
src/page-instrument/header-top/settting/index.module.less

@@ -1,4 +1,30 @@
 .settting{
+    &.follow{
+        .head{
+            background: url("../image/headImg1.png") no-repeat;
+            background-size: 100% 100%; 
+        }
+        .content{
+            background: #ACDDEA;
+            box-shadow: 0px 4px 0px 0px #5EA2B9;
+            .conBox{
+                background: #E3F3F5;
+            }
+        }
+    }
+    &.evaluating{
+        .head{
+            background: url("../image/headImg2.png") no-repeat;
+            background-size: 100% 100%; 
+        }
+        .content{
+            background: #B0CDFF;
+            box-shadow: 0px 4px 0px 0px #759CE4;
+            .conBox{
+                background: #EAF1FB;
+            }
+        }
+    }
     .head{
         background: url("../image/headImg.png") no-repeat;
         background-size: 100% 100%;
@@ -47,6 +73,9 @@
                 justify-content: space-between;
                 align-items: center;
                 border-bottom: 1px solid #D5E0ED;
+                &.isCamera{
+                    border-bottom: none;
+                }
                 :global{
                     .van-switch{
                         width: 42px;
@@ -70,6 +99,56 @@
                     color: #000000;
                     line-height: 21px;
                 }
+                .spendCon{
+                    flex-grow: 1;
+                    display: flex;
+                    align-items: center;
+                    padding: 3px 0;
+                    .sliderCon{
+                        padding-left: 28px;
+                        flex-grow: 1;
+                        :global{
+                            .van-slider{
+                                height: 10px;
+                                background: #94ACC4;
+                                box-shadow: inset 0px 2px 3px 0px #647F98;
+                                .van-slider__bar{
+                                    background: linear-gradient( 270deg, #7ADEFF 0%, #29A9FF 100%);
+                                    box-shadow: inset 1px 0px 5px 0px rgba(150,254,255,0.79);
+                                    border: 1px solid #4A91D4;
+                                    .van-slider__button-wrapper{
+                                        bottom: 0;
+                                        top: initial;
+                                        transform: translate3d(50%,12px,0);
+                                    }
+                                }
+                            }
+                        }
+                        .customButton{
+                            display: flex;
+                            flex-direction: column;
+                            align-items: center;
+                            .speedVal{
+                                width: 34px;
+                                height: 31px;
+                                background: url("../image/qipao.png") no-repeat;
+                                background-size: 100% 100%;
+                                font-weight: 600;
+                                font-size: 14px;
+                                color: #FFFFFF;
+                                line-height: 20px;
+                                text-align: center;
+                                padding-top: 3px;
+                            }
+                            .speedBtn{
+                                width: 16px;
+                                height: 30px;
+                                background: url("../image/speedBtn.png") no-repeat;
+                                background-size: 100% 100%;
+                            }
+                        }
+                    }
+                }
                 .radioBox{
                     display: flex;
                     > div{
@@ -158,19 +237,4 @@
             }
         }
     }
-}
-
-.slider {
-    width: 60%;
-    margin-right: 20px;
-
-    .sliderBtn {
-        width: 40px;
-        color: #fff;
-        font-size: 12px;
-        line-height: 20px;
-        text-align: center;
-        background-color: var(--van-primary-color);
-        border-radius: 20px;
-    }
 }

+ 117 - 67
src/page-instrument/header-top/settting/index.tsx

@@ -8,10 +8,13 @@ import { api_closeCamera, api_openCamera, api_savePicture } from "/src/helpers/c
 import { smoothAnimationState} from "/src/page-instrument/view-detail/smoothAnimation"
 import Recommendation from "../../custom-plugins/helper-model/recommendation";
 import { resetRenderMusicScore } from "/src/view/music-score";
+import ScreenModel from "../../custom-plugins/helper-model/screen-model";
+import { getQuery } from "/src/utils/queryString";
 
 export default defineComponent({
 	name: "settting",
 	setup() {
+        const query = getQuery();
         const helperData = reactive({
 			screenModelShow: false, // 投屏帮助
 			recommendationShow: false, // 建议
@@ -31,78 +34,110 @@ export default defineComponent({
 		}
         const formatterTimeMs = (value: any) => value = String(Math.min(3000, value));
 		return () => (
-			<div class={styles.settting}>
+			<div class={[styles.settting, styles[state.modeType]]}>
                 <div class={styles.head}>
 					<img class={styles.headTit} src={headImg("settingName.png")} />
 					<img class={styles.closeImg} src={headImg("closeImg.png")} onClick={()=>{ headTopData.settingMode = false }} />
 				</div>
                 <div class={styles.content}>
                     <div class={styles.conBox}>
-                        <div class={styles.cellBox}>
-                            <div class={styles.tit}>指法</div>
-                            <Switch v-model={state.setting.displayFingering}></Switch>
-                        </div>
-                        <div class={styles.cellBox}>
-                            <div class={styles.tit}>循环播放</div>
-                            <Switch v-model={state.setting.repeatAutoPlay}></Switch>
-                        </div>
-                        <div class={styles.cellBox}>
-                            <div class={styles.tit}>旋律线</div>
-                            <Switch v-model={smoothAnimationState.isShow.value}></Switch>
-                        </div>                            
-                        <div class={styles.cellBox}>
-                            <div class={styles.tit}>延迟检测</div>
-                            <Switch v-model={state.setting.soundEffect}></Switch>
-                        </div>   
-                        <div class={styles.cellBox}>
-                            <div class={styles.tit}>摄像头</div>
-                            <Switch 
-                                v-model={state.setting.camera}
-                                onChange={ async (value) => {
-                                    if (value) {
-                                        const res = await api_openCamera();
-                                        // 没有授权
-                                        if (res?.content?.reson) {
-                                            state.setting.camera = false
-                                        }
-                                    } else {
-                                        api_closeCamera();
-                                    }
-                                }}                                
-                            ></Switch>
-                        </div>   
-                        <div class={styles.cellBox} style={{ display: state.setting.camera ? "" : "none" }}>
-                            <div class={styles.tit}>透明度</div>
-                            <Slider
-                                class={styles.slider}
-                                min={0}
-                                max={100}
-                                v-model:modelValue={state.setting.cameraOpacity}
-                            >
-                                {{
-                                    button: () => <div class={styles.sliderBtn}>{state.setting.cameraOpacity}</div>,
-                                }}
-                            </Slider>
-                        </div>                                                                     
-                        <div class={styles.cellBox}>
-                            <div class={styles.tit}>标准音高</div>
-                            <div class={styles.frequency}>
-                                <img src={headImg("cutImg.png")} class={[styles.btn]} onClick={() => operateHz(1)} />
-                                <div class={styles.frequencyNum}>{state.setting.frequency}HZ</div>
-                                <img src={headImg("addImg.png")} class={[styles.btn]} onClick={() => operateHz(2)} />
-                            </div>
-                        </div>                        
-                        <div class={styles.cellBox}>
-                            <div class={styles.tit}>反应时间</div>
-                            <div class={styles.reactionTimeBox}>
-                                <Field class={styles.reactionTime} type="digit" 
-                                    placeholder="最大可输入3000毫秒"
-                                    formatter={formatterTimeMs}
-                                    input-align={'center'}
-                                    v-model:modelValue={state.setting.reactionTimeMs} />
-                                <div class={styles.timeName}>毫秒</div>    
-                            </div>
-                        </div>
+                        {
+                            state.isShowFingering && state.fingeringInfo.name && ["practise", "follow"].includes(state.modeType) &&
+                                <div class={styles.cellBox}>
+                                <div class={styles.tit}>指法</div>
+                                    <Switch v-model={state.setting.displayFingering}></Switch>
+                                </div>
+                        }
+                        {
+                            ["practise", "follow"].includes(state.modeType) &&                         
+                                <div class={styles.cellBox}>
+                                    <div class={styles.tit}>循环播放</div>
+                                    <Switch v-model={state.setting.repeatAutoPlay}></Switch>
+                                </div>
+                        }
+                        {
+                            state.isSingleLine && state.modeType === "practise" &&
+                                <div class={styles.cellBox}>
+                                <div class={styles.tit}>旋律线</div>
+                                    <Switch v-model={smoothAnimationState.isShow.value}></Switch>
+                                </div>   
+                        }                           
+                        {
+                            state.modeType === "evaluating" && 
+                            <>                       
+                                {
+                                    !query.workRecord &&                                 
+                                    <div class={styles.cellBox}>
+                                        <div class={styles.tit}>评测难度</div>
+                                        <div class={styles.radioBox}>
+                                            {
+                                                [{name:'入门',value:"BEGINNER"},{name:'进阶',value:"ADVANCED"},{name:'大师',value:"PERFORMER"}].map(item=>{
+                                                    return <div class={ state.setting.evaluationDifficulty===item.value && styles.active } onClick={ ()=>{
+                                                        state.setting.evaluationDifficulty = item.value as any
+                                                    } }>{item.name}</div>
+                                                })
+                                            }
+                                        </div>
+                                    </div>   
+                                }                     
+                                <div class={styles.cellBox}>
+                                    <div class={styles.tit}>延迟检测</div>
+                                    <Switch v-model={state.setting.soundEffect}></Switch>
+                                </div> 
+                                <div class={[styles.cellBox, state.setting.camera && styles.isCamera]}>
+                                    <div class={styles.tit}>摄像头</div>
+                                    <Switch 
+                                        v-model={state.setting.camera}
+                                        onChange={ async (value) => {
+                                            if (value) {
+                                                const res = await api_openCamera();
+                                                // 没有授权
+                                                if (res?.content?.reson) {
+                                                    state.setting.camera = false
+                                                }
+                                            } else {
+                                                api_closeCamera();
+                                            }
+                                        }}                                
+                                    ></Switch>
+                                </div>   
+                                <div class={styles.cellBox} style={{ display: state.setting.camera ? "" : "none", paddingTop: 0 }}>
+                                    <div class={styles.tit}>不透明度</div>
+                                    <div class={styles.spendCon}>
+                                        <div class={styles.sliderCon}>
+                                            <Slider class={styles.slider} max={100} min={0} v-model={state.setting.cameraOpacity}>
+                                                {{
+                                                    button: () => 
+                                                    <div class={styles.customButton}>
+                                                        <div class={styles.speedVal}>{ state.setting.cameraOpacity }</div>
+                                                        <div class={styles.speedBtn}></div>
+                                                    </div>
+                                                }}
+                                            </Slider>
+                                        </div>
+                                    </div>
+                                </div>                                                                     
+                                <div class={styles.cellBox}>
+                                    <div class={styles.tit}>标准音高</div>
+                                    <div class={styles.frequency}>
+                                        <img src={headImg("cutImg.png")} class={[styles.btn]} onClick={() => operateHz(1)} />
+                                        <div class={styles.frequencyNum}>{state.setting.frequency}HZ</div>
+                                        <img src={headImg("addImg.png")} class={[styles.btn]} onClick={() => operateHz(2)} />
+                                    </div>
+                                </div>                        
+                                <div class={styles.cellBox}>
+                                    <div class={styles.tit}>反应时间</div>
+                                    <div class={styles.reactionTimeBox}>
+                                        <Field class={styles.reactionTime} type="digit" 
+                                            placeholder="最大可输入3000毫秒"
+                                            formatter={formatterTimeMs}
+                                            input-align={'center'}
+                                            v-model:modelValue={state.setting.reactionTimeMs} />
+                                        <div class={styles.timeName}>毫秒</div>    
+                                    </div>
+                                </div>
+                            </>
+                        }
                         {/** 练习模式才有单行/多行谱切换功能,跟练、评测只有单行谱模式 */}
                         {
                             state.modeType === 'practise' ? 
@@ -139,7 +174,7 @@ export default defineComponent({
                             </div>
                         </div>
                         <div class={styles.cellBtnBox}>
-                            <img  src={headImg("tpbz.png")} />
+                            <img  src={headImg("tpbz.png")} onClick={() => (helperData.screenModelShow = true)} />
                             <img  src={headImg("yjfk.png")} onClick={() => (helperData.recommendationShow = true)} />
                         </div>
                     </div>  
@@ -157,6 +192,21 @@ export default defineComponent({
                         }}
                     />
 				</Popup>
+                <Popup
+					class={["popup-custom"]}
+					v-model:show={helperData.screenModelShow}
+					onClose={() => {
+						helperData.screenModelShow = false;
+					}}
+					position="right"
+					teleport="body"
+				>
+					<ScreenModel
+						onClose={(open: Boolean) => {
+							helperData.screenModelShow = false;
+						}}
+					/>
+				</Popup>
 			</div>
 		);
 	},

+ 26 - 0
src/page-instrument/header-top/speed/index.module.less

@@ -1,4 +1,30 @@
 .speedContainer{
+    &.follow{
+        .head{
+            background: url("../image/headImg1.png") no-repeat;
+            background-size: 100% 100%; 
+        }
+        .content{
+            background: #ACDDEA;
+            box-shadow: 0px 4px 0px 0px #5EA2B9;
+            .conBox{
+                background: #E3F3F5;
+            }
+        }
+    }
+    &.evaluating{
+        .head{
+            background: url("../image/headImg2.png") no-repeat;
+            background-size: 100% 100%; 
+        }
+        .content{
+            background: #B0CDFF;
+            box-shadow: 0px 4px 0px 0px #759CE4;
+            .conBox{
+                background: #EAF1FB;
+            }
+        }
+    }
     .head{
         background: url("../image/headImg.png") no-repeat;
         background-size: 100% 100%;

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

@@ -24,6 +24,14 @@ export default defineComponent({
 				handleSetSpeed(speed.value);
 			}
 		);
+		watch(
+			() => state.speed,
+			() => {
+				if (speed.value !== state.speed) {
+					speed.value = state.speed;
+				}
+			}
+		);
 		const metronomeDisable = computed({
 			get(){
 				return !metronomeData.disable
@@ -33,7 +41,7 @@ export default defineComponent({
 			}
 		})
 		return () => (
-			<div class={styles.speedContainer}>
+			<div class={[styles.speedContainer, styles[state.modeType]]}>
 				<div class={styles.head}>
 					<img class={styles.headTit} src={headImg("headTit.png")} />
 					<img class={styles.closeImg} src={headImg("closeImg.png")} onClick={()=>{ headData.speedShow = false }} />

+ 7 - 6
src/page-instrument/view-detail/index.tsx

@@ -90,7 +90,7 @@ export default defineComponent({
     const detailData = reactive({
       isLoading: true,
       skeletonLoading: true,
-      paddingLeft: "",
+      paddingLeft: "0px",
       headerHide: false,
       fingerPreView: false,
       fingerPreViewAnimation: false,
@@ -171,11 +171,11 @@ export default defineComponent({
       if (state.originSpeed === 0) {
         state.originSpeed = state.speed = (osmd as any).bpm || osmd.Sheet.userStartTempoInBPM || 100;
       }
-      const saveSpeed = (store.get("speeds") || {})[state.examSongId] || state.speed || (osmd as any).bpm || osmd.Sheet.userStartTempoInBPM;
-      // 加载本地缓存的速度
-      if (saveSpeed) {
-        handleSetSpeed(saveSpeed);
-      }
+      // const saveSpeed = (store.get("speeds") || {})[state.examSongId] || state.speed || (osmd as any).bpm || osmd.Sheet.userStartTempoInBPM;
+      // // 加载本地缓存的速度
+      // if (saveSpeed) {
+      //   handleSetSpeed(saveSpeed);
+      // }
       setCustomGradual();
 			setCustomNoteRealValue();
       state.times = formateTimes(osmd);
@@ -456,6 +456,7 @@ export default defineComponent({
       <div
         class={[styles.detail, styles[state.modeType], state.setting.eyeProtection && "eyeProtection", (state.platform === IPlatform.PC && state.zoom > 0.8) && styles.PC, state.isPreView && styles.preViewDetail]}
         style={{
+          '--detailDataPaddingLeft': detailData.paddingLeft,
           paddingLeft: detailData.paddingLeft,
           background: state.setting.camera ? `rgba(${state.setting.eyeProtection ? "253,244,229" : "255,255,255"} ,${state.setting.cameraOpacity / 100}) !important` : "",
         }}

+ 5 - 10
src/page-instrument/view-detail/smoothAnimation/bird/index.tsx

@@ -10,16 +10,11 @@ export default defineComponent({
     name: "Bird",
     setup() {
         return () => <>
-                {
-                    state.playState === "paused" ?
-                        <Vue3Lottie key={1} class={styles.brid} animationData={stilln} autoPlay={true} loop={true}></Vue3Lottie>
-                        :
-                        <>
-                            <Vue3Lottie key={2} class={styles.brid} animationData={fly} autoPlay={true} loop={true}></Vue3Lottie>
-                            <Vue3Lottie key={2} class={styles.note} animationData={note} autoPlay={true} loop={true}></Vue3Lottie>
-                        </>
-                }
-            
+                <Vue3Lottie style={ { display : state.playState === "paused" ? "" : "none" } } key={1} class={styles.brid} animationData={stilln} autoPlay={true} loop={true}></Vue3Lottie>
+                <>
+                    <Vue3Lottie style={ { display : state.playState === "paused" ? "none" : "" } } key={2} class={styles.brid} animationData={fly} autoPlay={true} loop={true}></Vue3Lottie>
+                    <Vue3Lottie style={ { display : state.playState === "paused" ? "none" : "" } } key={2} class={styles.note} animationData={note} autoPlay={true} loop={true}></Vue3Lottie>
+                </>
         </>
     }
 })

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

@@ -5,6 +5,7 @@
         height: 2.4rem;
         &.smoothAnimationBoxHide{
             opacity: 0;
+            visibility: hidden;
         }
     }
     .smoothAnimationCon{

+ 3 - 2
src/page-instrument/view-detail/smoothAnimation/index.ts

@@ -78,7 +78,8 @@ export function initSmoothAnimation() {
    // 当前屏幕的宽度
    calcClientWidth()
    document.addEventListener("resize", calcClientWidth)
-   smoothAnimationState.isShow.value = true
+   // 初始化 只有练习模式 才显示
+   state.modeType === "practise" && (smoothAnimationState.isShow.value = true)
    console.log(smoothAnimationState, "一行谱小鸟数据")
 }
 
@@ -285,7 +286,7 @@ function createSmoothAnimation() {
    }, 0)
    // box
    const smoothAnimationBoxDom = document.createElement("div")
-   smoothAnimationBoxDom.className = "smoothAnimationBox"
+   smoothAnimationBoxDom.className = "smoothAnimationBox smoothAnimationBoxHide"
    smoothAnimationState.smoothAnimationBoxDom = smoothAnimationBoxDom
    // con
    const smoothAnimationConDom = document.createElement("div")

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

@@ -459,7 +459,7 @@
     flex-wrap: wrap;
     margin-top: 16px;
     position: relative;
-
+    
     .item {
         width: 50%;
         display: flex;

+ 88 - 49
src/state.ts

@@ -505,6 +505,10 @@ const state = reactive({
   loadingText: '音频资源加载中,请稍后…',
   /** 是否是简单的单行谱模式页面 */
   isSimplePage: false, 
+  /** xml的速度和后台设置的速度,计算出的基础音频播放倍率 */
+  originAudioPlayRate: 1,  
+  /** 开始播放时,记录的mp3播放倍率,用户当前设置的速度/当前小节的速度 */
+  basePlayRate: 1,
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -580,6 +584,35 @@ export const onEnded = () => {
   autoResetPlay();
 };
 
+// 根据当前小节动态设置,右上角展示的速度
+const dynamicShowPlaySpeed = (index: number) => {
+  const item: any = state.times[index];
+  if (item && item.measureSpeed ) {
+    state.playIngSpeed = Math.floor(state.basePlayRate * item.measureSpeed)
+    state.speed = state.playIngSpeed
+  }
+}
+
+// 开始播放时,计算mp3的播放倍率
+export const initSetPlayRate = () => {
+  const item: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
+  if (item && item.measureSpeed) {
+    const ratio = state.speed / item.measureSpeed
+    // state.audiosInstance?.setSpeed(ratio)
+    state.basePlayRate = ratio || 1;
+    console.log('播放倍率',state.basePlayRate)
+  }
+}
+
+// 重置播放倍率
+export const resetBaseRate = () => {
+  const currentItem: any = state.times[0];
+  const currentSpeed = currentItem?.measureSpeed ? currentItem.measureSpeed : state.originSpeed;
+  state.speed = currentSpeed
+  //state.activeNoteIndex = 0
+  state.basePlayRate = 1;
+}
+
 /**
  * 播放一直触发的事件
  */
@@ -588,17 +621,8 @@ const handlePlaying = () => {
   const duration = getAudioDuration();
   state.playProgress = (currentTime / duration) * 100;
   let item = getNote(currentTime);
-  // console.log(11111,currentTime,duration,state.playSource, item)
-  // console.log(item?.i,item?.noteId,item?.measureSpeed,'播放')
-  // 练习模式下,实时刷新小节速度
-  if (item && state.modeType === "practise" && state.playState === "play" && item.measureSpeed && item.measureSpeed !== state.playIngSpeed) {
-    const ratio = state.speed / state.originSpeed
-    state.playIngSpeed = Math.ceil(ratio * item.measureSpeed) || state.speed
-  } else if (state.modeType === "practise" && state.playState === "play" && item && !item.measureSpeed) {
-    state.playIngSpeed = state.speed
-  }
-  state.playIngSpeed = state.playIngSpeed || state.speed;
   if (item) {
+    dynamicShowPlaySpeed(item.i);
     // 选段状态下
     if (state.sectionStatus && state.section.length === 2) {
       // 如果开启了预备拍
@@ -641,6 +665,8 @@ const handlePlaying = () => {
         // #8698 bug修复
         if (state.modeType === "practise" && state.sectionStatus) {
           onEnded();
+          // state.activeNoteIndex = state.sectionFirst ? state.sectionFirst.i : state.section[0].i
+          // dynamicShowPlaySpeed(state.activeNoteIndex)
           resetPlaybackToStart();
           return;
         }
@@ -670,6 +696,10 @@ export const skipNotePlay = async (itemIndex: number, isStart = false) => {
     itemTime = 0;
   }
   if (item) {
+    // 非选段模式,点击音符,动态设置右下角的速度
+    if (item.measureSpeed && state.section.length < 2) {
+      state.speed = Math.floor(state.basePlayRate * item.measureSpeed)
+    }
     setAudioCurrentTime(itemTime, itemIndex);
     // 一行谱,点击音符,或者播放完成,需要跳转音符位置
     gotoNext(item, true);
@@ -753,6 +783,7 @@ export const togglePlay = async (playState?: "play" | "paused", sourceType?: str
       clearSelection();
     }
   }
+  initSetPlayRate();
   audioListStart(state.playState);
   return true;
 };
@@ -995,14 +1026,20 @@ export const handleResetPlay = () => {
   if (state.isAppPlay) {
     audioData.progress = 0
   }
+  resetBaseRate();
   resetPlaybackToStart();
   // 如果是暂停, 直接播放
   togglePlay("play");
 };
 /** 设置速度 */
 export const handleSetSpeed = (speed: number) => {
-  setStorageSpeed(state.examSongId, speed);
+  // setStorageSpeed(state.examSongId, speed);
   state.speed = speed;
+  // 当前的音符
+  const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
+  state.basePlayRate = currentItem?.measureSpeed ? state.speed / currentItem.measureSpeed : state.speed / state.originSpeed;
+  const actualRate = state.originAudioPlayRate * state.basePlayRate;
+  console.log('速度设置',speed,'小节计算的倍率',state.basePlayRate,'实际播放倍率',actualRate)
 };
 /** 清除选段状态 */
 export const clearSelection = () => {
@@ -1017,7 +1054,7 @@ export const handleChangeSection = () => {
   if (state.sectionStatus) {
     togglePlay("paused");
     clearSelection();
-    skipNotePlay(0, true);
+    //skipNotePlay(0, true);  取消选段的时候 不跳回开头
     state.sectionFirst = null;
     return;
   }
@@ -1127,7 +1164,7 @@ export const setSection = (start: number, end: number, userSpeed?: number) => {
     // 设置小节
     hanldeDirectSelection([startNote, endNote]);
 
-    //设置速度
+    // 评测作业,练习作业的场景,需要用老师布置的速度,设置播放速度
     if (userSpeed) {
       handleSetSpeed(userSpeed);
     }
@@ -1141,6 +1178,11 @@ export const hanldeDirectSelection = (list: any[]) => {
   state.sectionStatus = true;
   setTimeout(() => {
     state.section = formateSelectMearure(list);
+    // 选段完成后,需要根据预报小节的速度,设置右下角显示的速度
+    const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
+    if (currentItem.measureSpeed && query.workRecord === undefined) {
+      handleSetSpeed(currentItem.measureSpeed);
+    }
     console.log('选段小节', state.section)
   }, 0);
 };
@@ -1316,6 +1358,10 @@ const setState = (data: any, index: number) => {
   state.coverImg = data.musicCover ?? "";
   // 单声部多声轨合并展示
   state.isCombineRender = data.musicSheetType === "SINGLE" && data.musicSheetSoundList?.length > 1
+  // 如果是simple页面,只显示单轨
+  if (state.isSimplePage) {
+    state.isCombineRender = false;
+  }
   setCustom(state.isCombineRender ? data.musicSheetSoundList?.length : 0);
   // 解析扩展字段
   if (data.extConfigJson) {
@@ -1350,12 +1396,11 @@ const setState = (data: any, index: number) => {
   const track = data.code || data.track;
   state.track = track ? track.replace(/ /g, "").toLocaleLowerCase() : "";
   // 能否评测,根据当前声轨有无伴奏判断
-  // if (state.isAppPlay) {
-  //   state.enableEvaluation = state.midiUrl ? true : false
-  // } else {
-  //   state.enableEvaluation = state.accompany ? true : false
-  // }
-  state.enableEvaluation = true
+  if (state.isAppPlay) {
+    state.enableEvaluation = state.midiUrl ? true : false
+  } else {
+    state.enableEvaluation = state.accompany || state.music ? true : false
+  }
   state.isConcert = data.musicSheetType === "CONCERT" ? true : false;
   // multiTracksSelection 返回为空,默认代表全部分轨
   state.canSelectTracks = data.multiTracksSelection === "null" || data.multiTracksSelection === "" || data.multiTracksSelection === null ? [] : data.multiTracksSelection?.split(',');
@@ -1622,11 +1667,13 @@ watch(
   () => state.activeMeasureIndex,
   () => {
     // 需要减去的合并小节数
-    const needReduceMultipleRestNum = getNeedReduceMultipleRestNum(state.activeMeasureIndex)
-    const matchMeasureNum = state.activeMeasureIndex - needReduceMultipleRestNum - 1
-    console.log('选中的小节', matchMeasureNum, '需要减去的小节', needReduceMultipleRestNum, '当前的小节', state.activeMeasureIndex)
+    // const needReduceMultipleRestNum = getNeedReduceMultipleRestNum(state.activeMeasureIndex)
+    // const matchMeasureNum = state.activeMeasureIndex - needReduceMultipleRestNum - 1
+    // console.log('选中的小节',matchMeasureNum,'需要减去的小节',needReduceMultipleRestNum,'当前的小节',state.activeMeasureIndex)
     state.vfmeasures.forEach((item: any, idx: number) => {
-      if (idx === matchMeasureNum) {
+      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)) ) {
         item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#132D4C")
         item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#040D1E")
       } else {
@@ -1639,18 +1686,14 @@ watch(
             leftMeasureNumberXML = state.section[1].MeasureNumberXML
             rightMeasureNumberXML = state.section[0].MeasureNumberXML
           }
-          const leftVfmeasuresIndex = leftMeasureNumberXML - getNeedReduceMultipleRestNum(leftMeasureNumberXML) - 1
-          const rightVfmeasuresIndex = rightMeasureNumberXML - getNeedReduceMultipleRestNum(rightMeasureNumberXML) - 1
-          if (idx >= leftVfmeasuresIndex && idx <= rightVfmeasuresIndex) {
+          if(measureNum >= leftMeasureNumberXML && measureNum <= rightMeasureNumberXML){
             item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#609FCF")
             item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#2B70A5")
           }
           // 预备小节
-          if (state.sectionFirst) {
-            const sectionFirstVfmeasuresIndex = state.sectionFirst.MeasureNumberXML - getNeedReduceMultipleRestNum(state.sectionFirst.MeasureNumberXML) - 1
-            const sectionFirstDom = state.vfmeasures[sectionFirstVfmeasuresIndex]
-            sectionFirstDom?.querySelector('.vf-custom-bg')?.setAttribute("fill", "#71B8BD")
-            sectionFirstDom?.querySelector('.vf-custom-bot')?.setAttribute("fill", "#448F9C")
+          if(state.sectionFirst && measureNum === state.sectionFirst.MeasureNumberXML){
+            item?.querySelector('.vf-custom-bg')?.setAttribute("fill", "#71B8BD")
+            item?.querySelector('.vf-custom-bot')?.setAttribute("fill", "#448F9C")
           }
         } else {
           item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#609FCF")
@@ -1674,32 +1717,30 @@ watch(
         leftMeasureNumberXML = state.section[1].MeasureNumberXML
         rightMeasureNumberXML = state.section[0].MeasureNumberXML
       }
-      const leftVfmeasuresIndex = leftMeasureNumberXML - getNeedReduceMultipleRestNum(leftMeasureNumberXML) - 1
-      const rightVfmeasuresIndex = rightMeasureNumberXML - getNeedReduceMultipleRestNum(rightMeasureNumberXML) - 1
       state.vfmeasures.forEach((item: any, idx: number) => {
+        const measureNum = item.getAttribute('data-num') ? Number(item.getAttribute('data-num')) : 1;
         // 小于选中置灰
-        if (idx < leftVfmeasuresIndex) {
+        if (measureNum < leftMeasureNumberXML) {
           item.querySelector('.vf-custom-bg')?.setAttribute("fill", "rgba(96,159,207,0.5)")
           item.querySelector('.vf-custom-bot')?.setAttribute("fill", "rgba(43,112,165,0.5)")
         }
         // 大于选中置灰
-        if (idx > rightVfmeasuresIndex) {
+        if(measureNum > rightMeasureNumberXML){
           item.querySelector('.vf-custom-bg')?.setAttribute("fill", "rgba(96,159,207,0.5)")
           item.querySelector('.vf-custom-bot')?.setAttribute("fill", "rgba(43,112,165,0.5)")
         }
+        // 预备小节
+        if(state.sectionFirst && measureNum === state.sectionFirst.MeasureNumberXML){
+          item?.querySelector('.vf-custom-bg')?.setAttribute("fill", "#71B8BD")
+          item?.querySelector('.vf-custom-bot')?.setAttribute("fill", "#448F9C")
+        }        
       })
-      // 预备小节
-      if (state.sectionFirst) {
-        const sectionFirstVfmeasuresIndex = state.sectionFirst.MeasureNumberXML - getNeedReduceMultipleRestNum(state.sectionFirst.MeasureNumberXML) - 1
-        const sectionFirstDom = state.vfmeasures[sectionFirstVfmeasuresIndex]
-        sectionFirstDom?.querySelector('.vf-custom-bg')?.setAttribute("fill", "#71B8BD")
-        sectionFirstDom?.querySelector('.vf-custom-bot')?.setAttribute("fill", "#448F9C")
-      }
-    } else {
+    }else{
       // 恢复选段前
-      const matchMeasureNum = state.activeMeasureIndex - getNeedReduceMultipleRestNum(state.activeMeasureIndex) - 1
       state.vfmeasures.forEach((item: any, idx: number) => {
-        if (idx === matchMeasureNum) {
+        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)) ) {
           item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#132D4C")
           item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#040D1E")
         } else {
@@ -1714,10 +1755,8 @@ watch(
 /** 刷新谱面 */
 export const refreshMusicSvg = () => {
   state.loadingText = '正在加载中,请稍等…'
-  if (state.isSingleLine) {
-    // 销毁旋律线
-    destroySmoothAnimation()
-  }
+  // 销毁旋律线
+  destroySmoothAnimation()
   musicScoreRef.value?.refreshMusicScore()
 }
 

+ 1 - 0
src/utils/index.ts

@@ -97,6 +97,7 @@ export const setStorageSpeed = (id: any, speed: number) => {
 /** 获取曲谱速度 */
 export const getStorageSpeed = (id: any) => {
 	const speeds = store.get(SPEEDKEY) || {}
+	console.log('初始速度', speeds)
 	return speeds[id] || 0
 }
 

BIN
src/view/audio-list/img/refresh_anim.gif


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

@@ -39,7 +39,9 @@ const midiRef = ref();
 export const audioListStart = (type: "play" | "paused") => {
 	// 开始播放之前, 先设置倍数
 	if (type === "play" && state.originSpeed !== 0) {
-		setAudioPlaybackRate(state.speed / state.originSpeed);
+		const actualRate = state.originAudioPlayRate * state.basePlayRate;
+		// console.log('音频播放倍率',actualRate)
+		setAudioPlaybackRate(actualRate);
 	}
 	// console.log('api','midi状态1',type,state.isAppPlay)
 	// 如果是midi播放

+ 3 - 27
src/view/audio-list/loading.tsx

@@ -5,6 +5,7 @@ import styles from "./index.module.less"
 import state from "/src/state"
 import { Vue3Lottie } from "vue3-lottie";
 import animBg from "./img/refresh_anim.json";
+import animGif from "./img/refresh_anim.gif";
 
 export default defineComponent({
    name: "loading",
@@ -16,36 +17,11 @@ export default defineComponent({
 		},
 	},
    setup(props) {
-      function fakeLoadingProgress(duration = 2000, callback: (num: number) => void) {
-         let startTime = Date.now()
-         let progress = 0
-         const timer = setInterval(() => {
-            let timePassed = Date.now() - startTime
-            if (timePassed >= duration) {
-               clearInterval(timer)
-               callback(96) // 进度完成
-               return
-            }
-            progress = Math.min(100, (timePassed / duration) * 100)
-            callback(progress)
-         }, 300)
-      }
-      const loadingProress = ref(0)
-      fakeLoadingProgress(2000, num => {
-         loadingProress.value = num
-      })
-      watch(
-         () => state.audioDone,
-         () => {
-           if (!state.audioDone) {
-            loadingProress.value = 0
-           }
-         }
-       );
       return () =>
          !state.audioDone && (
             <div class={styles.loadingPop}>
-               <Vue3Lottie class={styles.lottie} animationData={animBg}></Vue3Lottie>
+               <img class={styles.lottie} src={animGif} />
+               {/* <Vue3Lottie class={styles.lottie} animationData={animBg}></Vue3Lottie> */}
                <div class={styles.loadingTip}>{props.tipText}</div>
             </div>
          )

+ 1 - 1
src/view/music-score/index.module.less

@@ -30,7 +30,7 @@
         }
         transform-box: fill-box;
         transform-origin: center;
-        animation: noteAnimate 0.3s linear;
+        // animation: noteAnimate 0.3s linear;
         // transform: scale(2);
         // transition: all 0.3s;
     }

+ 2 - 2
vite.config.ts

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