소스 검색

节奏律动

黄琪勇 5 달 전
부모
커밋
c921cbbfaf

BIN
src/page-instrument/custom-plugins/guide-driver/images/practise/d777.png


+ 12 - 0
src/page-instrument/custom-plugins/guide-driver/index.less

@@ -124,6 +124,18 @@
   }
 }
 
+.popoverClass777 {
+  width: 265px;
+  height: 245px;
+  background: url("./images/practise/d777.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 25px;
+    bottom: 23px;
+  }
+}
+
 .popoverClass5 {
   width: 264px;
   height: 245px;

+ 17 - 2
src/page-instrument/custom-plugins/guide-driver/index.tsx

@@ -169,6 +169,21 @@ export const PractiseDriver = defineComponent({
       }
       options.steps?.push(
         {
+          element: ".driver-777",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass777",
+            align: "start",
+            side: "top",
+            nextBtnText: `下一步 (${options.steps.length + 1}/${length})`,
+            showButtons: ["next"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              driverInitialPosition(popover, options);
+            },
+          },
+        },
+        {
           element: ".driver-4",
           popover: {
             title: "",
@@ -176,7 +191,7 @@ export const PractiseDriver = defineComponent({
             popoverClass: "popoverClass popoverClass4",
             align: "start",
             side: "top",
-            nextBtnText: `下一步 (${options.steps.length + 1}/${length})`,
+            nextBtnText: `下一步 (${options.steps.length + 2}/${length})`,
             showButtons: ["next"],
             onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
               driverInitialPosition(popover, options);
@@ -191,7 +206,7 @@ export const PractiseDriver = defineComponent({
             popoverClass: "popoverClass popoverClass5",
             align: "start",
             side: "top",
-            nextBtnText: `下一步 (${options.steps.length + 2}/${length})`,
+            nextBtnText: `下一步 (${options.steps.length + 3}/${length})`,
             showButtons: ["next"],
             onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
               driverInitialPosition(popover, options);

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


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


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

@@ -291,6 +291,22 @@
             }
 
             ;
+        }      
+        &.rhythmMode:active {
+            >img {
+                content: url("./image/rhythmAct.png");
+            }
+        }  
+        &.isrhythmMode {
+            >img {
+                content: url("./image/rhythmAct.png");
+            }
+
+            >span {
+                color: #0EA7FF
+            }
+
+            ;
         }
     }
 

+ 50 - 2
src/page-instrument/header-top/index.tsx

@@ -48,6 +48,10 @@ export const headTopData = reactive({
   showBack: true,
   /** 设置弹窗 */
   settingMode: false,
+  /* 节奏律动 */
+  rhythmMode: false,
+  // 节奏律动方向
+  rhythmModeDirection: computed(()=> state.fingeringInfo.direction === "transverse" ? "vertical" : "transverse"),
   /** 切换模式 */
   handleChangeModeType(value: "practise" | "follow" | "evaluating") {
     // 后台设置为不能评测
@@ -83,6 +87,8 @@ export const headTopData = reactive({
       //   state.isSingleLine = true;
       //   refreshMusicSvg();
       // }
+      // 关闭节奏律动
+      headTopData.rhythmMode = false
       smoothAnimationState.isShow.value = false; // 隐藏旋律线
       state.playIngSpeed = state.originSpeed;
       handleStartEvaluat();
@@ -91,6 +97,8 @@ export const headTopData = reactive({
       // evaluatingData.rendered = true;
       // evaluatingData.soundEffectMode = true;
     } else if (value === "follow") {
+      // 关闭节奏律动
+      headTopData.rhythmMode = false
       // 跟练模式,只有一行谱模式
       if (!state.isSingleLine) {
         state.isSingleLine = true;
@@ -436,6 +444,33 @@ export default defineComponent({
       };
     });
 
+    /** 节奏律动 */
+    const rhythmBtn = computed(() => {
+      // 跟练和评测显示
+      if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: false, playIng: false };
+      // 播放过程中不让切换
+      if (state.playState == "play") {
+        if(headTopData.rhythmMode) {
+          return { 
+            display: true, 
+            disabled: true,
+            playIng: true 
+          }
+        }else{
+          return { 
+            display: true, 
+            disabled: true,
+            playIng: false
+          }
+        }
+      };
+      return {
+        display: true,
+        disabled: false,
+        playIng: false
+      };
+    });
+
     /** 重播按钮 */
     resetBtn = computed(() => {
       // 没有音源不显示
@@ -746,7 +781,7 @@ export default defineComponent({
           }
 
           {/* 模式提醒 */}
-          {state.modeType === "practise" && (
+          {state.modeType === "practise" && !rhythmBtn.value.playIng && (
             <div class={[styles.modeWarn, "practiseModeWarn", state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.modeWarnRight : ""]}>
               <img src={state.playType === "play" ? headImg("perform1.png") : headImg("sing1.png")} />
               <div>{state.playType === "play" ? "演奏场景" : "演唱场景"}</div>
@@ -866,6 +901,16 @@ export default defineComponent({
               <span>{state.playSource === "music" ? (state.playType === "play" ? "原声" : "范唱") : state.playSource === "background" ? (state.playType === "play" ? "伴奏" : "伴唱") : "唱名"}</span>
             </div>
             <div
+                style={{ display: rhythmBtn.value.display ? "" : "none" }}
+                class={["driver-777", styles.btn, styles.rhythmMode, headTopData.rhythmMode && styles.isrhythmMode, rhythmBtn.value.disabled && styles.disabled]}
+                onClick={() => {
+                  headTopData.rhythmMode = !headTopData.rhythmMode
+                }}
+              >
+                <img class={styles.iconBtn} src={headImg(`rhythm.png`)} />
+                <span>律动</span>
+            </div>
+            <div
               id={state.platform === IPlatform.PC ? "teacherTop-2" : "studnetT-2"}
               style={{ display: selectBtn.value.display ? "" : "none" }}
               class={["driver-4", styles.btn, selectBtn.value.disabled && styles.disabled, styles.section, state.sectionStatus && styles.isSection]}
@@ -956,7 +1001,10 @@ export default defineComponent({
         {/* 播放按钮 */}
         <div
           id="studnetT-7"
-          style={{ display: playBtn.value.display ? "" : "none" }}
+          style={{ 
+            display: playBtn.value.display ? "" : "none" ,
+            opacity: rhythmBtn.value.playIng? "0.4" : "1"
+          }}
           class={[
             // 引导使用的类
             "driver-1",

+ 61 - 2
src/page-instrument/view-detail/index.tsx

@@ -9,7 +9,7 @@ import MusicScore from "../../view/music-score";
 import TestCheck from "/src/view/music-score/testCheck";
 import { sysMusicScoreAccompanimentQueryPage } from "../api";
 import EvaluatModel from "../evaluat-model";
-import HeaderTop, { handlerModeChange } from "../header-top";
+import HeaderTop, { handlerModeChange, headTopData } from "../header-top";
 import styles from "./index.module.less";
 import { api_cloudAccompanyMessage, api_cloudLoading, api_keepScreenLongLight, api_openCamera, api_openWebView, api_setEventTracking, api_setRequestedOrientation, api_setStatusBarVisibility, isSpecialShapedScreen } from "/src/helpers/communication";
 import { getQuery } from "/src/utils/queryString";
@@ -17,6 +17,7 @@ import Evaluating, { evaluatingData } from "/src/view/evaluating";
 import MeasureSpeed from "/src/view/plugins/measure-speed";
 import { mappingVoicePart, subjectFingering } from "/src/view/fingering/fingering-config";
 import Fingering from "/src/view/fingering";
+import Rhythm from "/src/view/rhythm";
 import store from "store";
 import Tick, { handleInitTick } from "/src/view/tick";
 import FollowPractice, { followData } from "/src/view/follow-practice";
@@ -398,6 +399,57 @@ export default defineComponent({
       };
     });
 
+    /** 节奏律动配置  */
+    const rhythmConfig = computed<any>(()=>{
+      if(headTopData.rhythmMode){
+        if(headTopData.rhythmModeDirection === "vertical"){
+          if(state.platform === IPlatform.PC && state.playBtnDirection === "left") {
+            return {
+              container: {
+                paddingLeft: "4.6rem"
+              },
+              rhythmBox: {
+                width: "4.6rem",
+                position: "absolute",
+                height: "100%",
+                left: 0,
+                top: 0
+              },
+            };
+          }else{
+            return {
+              container: {
+                paddingRight: "4.6rem"
+              },
+              rhythmBox: {
+                width: "4.6rem",
+                position: "absolute",
+                height: "100%",
+                right: 0,
+                top: 0
+              },
+            };
+          }
+        }else{
+          return {
+            container: {
+              paddingBottom: "2.5rem"
+            },
+            rhythmBox: {
+              height: "2.5rem",
+              position: "absolute",
+              width: "100%",
+              left: 0,
+              bottom: 0,
+            },
+          };
+        }
+      }
+      return {
+        container: {},
+        rhythmBox: {},
+      }; 
+    })
     // 监听指法显示
     watch(
       () => state.setting.displayFingering,
@@ -545,7 +597,7 @@ export default defineComponent({
         {!state.isPreView && <div class={["headHeight", styles.headHeight, headerColumnHide.value && styles.headHide]}>{state.musicRendered && <HeaderTop />}</div>}
         <div
           id="scrollContainer"
-          style={{ ...fingerConfig.value.container }}
+          style={{ ...fingerConfig.value.container, ...rhythmConfig.value.container }}
           class={[styles.container, !state.setting.displayCursor && "hideCursor", browsInfo.xiaomi && styles.xiaomi, state.platform === IPlatform.PC && styles.pcContainer]}
           onClick={(e: Event) => {
             e.stopPropagation();
@@ -590,6 +642,13 @@ export default defineComponent({
               />
             </div>
           )}
+          {/* 节奏律动 */}
+          {
+            headTopData.rhythmMode &&  
+            <div style={{ ...rhythmConfig.value.rhythmBox}}>
+              <Rhythm></Rhythm>
+            </div>
+          }
         </div>
         {/* 曲目渲染完成,再去下载mp3资源 */}
         {!detailData.isLoading && !detailData.skeletonLoading && <AudioList />}

+ 18 - 0
src/state.ts

@@ -2233,4 +2233,22 @@ watch(
       refreshMusicSvg();
     }
   }
+)
+
+
+// 节奏律动改变显示的时候 osdmScrollDomWith 宽度会变化 所以指法改变的时候这个宽度重新计算
+watch(
+  () => headTopData.rhythmMode,
+  () => {
+    // 竖向 并且是一行谱
+    if(headTopData.rhythmModeDirection === "vertical" && state.isSingleLine){
+      nextTick(() => {
+        calcClientWidth()
+      })
+    }
+    // 竖向时,谱面宽度变化,需要重新渲染谱面
+    if (headTopData.rhythmModeDirection === "vertical" && !state.isSingleLine) {
+      refreshMusicSvg();
+    }
+  }
 )

BIN
src/view/rhythm/imgs/1.png


BIN
src/view/rhythm/imgs/2.png


BIN
src/view/rhythm/imgs/3.png


BIN
src/view/rhythm/imgs/4.png


BIN
src/view/rhythm/imgs/5.png


BIN
src/view/rhythm/imgs/6.png


BIN
src/view/rhythm/imgs/7.png


BIN
src/view/rhythm/imgs/tit.png


+ 103 - 0
src/view/rhythm/index.module.less

@@ -0,0 +1,103 @@
+.rhythm{
+    width: 100%;
+    height: 100%;
+    position: relative;
+    .titImg{
+        position: absolute;
+        left: 30px;
+        top: 50%;
+        transform: translateY(-50%);
+        width: 100px;
+        height: 38px;
+        background: url("./imgs/tit.png") no-repeat;
+        background-size: 100% 100%;
+    }
+    .rhythmBox{
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        .rhythmImg{
+            width: 50px;
+            height: 50px;
+            background: #FFFFFF;
+            border-radius: 14px;
+            background-size: 44px 44px;
+            background-repeat: no-repeat;
+            background-position: center;
+            margin-right: 14px;
+            &:first-child{
+                width: 74px;
+                height: 74px;
+                background-size: 74px 74px;
+                background-color: #35A8FF;
+                border-radius: 20px;
+                outline: 3px solid #FFFFFF;
+            }
+            &:last-child{
+                margin-right: 0;
+            }
+            &.rhythm1{
+                background-image: url("./imgs/1.png");
+            }
+            &.rhythm2{
+                background-image: url("./imgs/2.png");
+            }
+            &.rhythm3{
+                background-image: url("./imgs/3.png");
+            }
+            &.rhythm4{
+                background-image: url("./imgs/4.png");
+            }
+            &.rhythm5{
+                background-image: url("./imgs/5.png");
+            }
+            &.rhythm6{
+                background-image: url("./imgs/6.png");
+            }
+            &.rhythm7{
+                background-image: url("./imgs/7.png");
+            }
+        }
+    }
+    &.vertical{
+        overflow: hidden;
+        background: linear-gradient( 180deg, #AFE8FF 0%, #D9F3FE 100%);
+        .titImg{
+            top: 10px;
+            left: 50%;
+            transform: translateX(-50%);
+        }
+        .rhythmBox{
+            position: initial;
+            transform: initial;
+            margin-top: 68px;
+            flex-wrap: wrap;
+            .rhythmImg{
+                margin-bottom: 20px;
+                width: 40px;
+                height: 40px;
+                border-radius: 10px;
+                background-size: 36px 36px;
+                &:nth-child(4){
+                    margin-right: 0;
+                }
+                &:nth-child(7){
+                    margin-right: 0;
+                }
+                &:first-child{
+                    margin-left: 20px;
+                    margin-right: 20px;
+                    width: 110px;
+                    height: 110px;
+                    background-size: 110px 110px;
+                    border-radius: 28px;
+                    outline: 4px solid #FFFFFF;
+                }
+            }
+        }
+    }
+}

+ 64 - 0
src/view/rhythm/index.tsx

@@ -0,0 +1,64 @@
+import { computed, defineComponent, onBeforeMount, ref } from "vue"
+import styles from "./index.module.less"
+import state from "/src/state"
+import { headTopData } from "/src/page-instrument/header-top"
+
+export default defineComponent({
+   name: "rhythm",
+   setup(props, { emit, expose }) {
+      type rhythmData = {
+         isDot: boolean
+         denominator: number
+         isRestFlag: boolean
+         rhythmImg: string
+      }
+      const rhythmImgObj: Record<string, any> = {
+         "1/1": "1",
+         "1/2": "2",
+         "1/4": "3",
+         "1/8": "4",
+         "1/16": "5",
+         "1/32": "6",
+         "1/1.": "1",
+         "1/2.": "2",
+         "1/4.": "3",
+         "1/8.": "4",
+         "1/16.": "5",
+         "1/32.": "6",
+         rest: "7"
+      }
+      const rhythmData = ref<rhythmData[]>([])
+      const actRhythmData = computed<rhythmData[]>(() => {
+         return rhythmData.value.slice(state.activeNoteIndex, state.activeNoteIndex + 7)
+      })
+      function initRhythmData() {
+         rhythmData.value = state.times.map(item => {
+            const noteElement = item.noteElement
+            const isDot = !!noteElement?.DotsXml
+            const denominator = noteElement?.typeLength?.denominator || 4
+            const isRestFlag = !!noteElement?.isRestFlag
+            return {
+               isDot,
+               denominator,
+               isRestFlag,
+               rhythmImg: isRestFlag ? "rest" : `1/${denominator}${isDot ? "." : ""}`
+            }
+         })
+      }
+      initRhythmData()
+      return () => {
+         return (
+            <>
+               <div class={[styles.rhythm, headTopData.rhythmModeDirection === "vertical" && styles.vertical]}>
+                  <div class={styles.titImg}></div>
+                  <div class={styles.rhythmBox}>
+                     {actRhythmData.value.map(item => {
+                        return <div class={[styles.rhythmImg, styles[`rhythm${rhythmImgObj[item.rhythmImg]}`]]}></div>
+                     })}
+                  </div>
+               </div>
+            </>
+         )
+      }
+   }
+})