Browse Source

添加练习模式功能引导

lex 11 tháng trước cách đây
mục cha
commit
4e4fe86922
27 tập tin đã thay đổi với 1794 bổ sung1344 xóa
  1. 1 1
      osmd-extended
  2. 16 10
      package-lock.json
  3. 1 0
      package.json
  4. 1 0
      src/page-instrument/component/mode-type-mode/index.tsx
  5. BIN
      src/page-instrument/custom-plugins/guide-driver/images/btn-close.png
  6. BIN
      src/page-instrument/custom-plugins/guide-driver/images/btn-finsh.png
  7. BIN
      src/page-instrument/custom-plugins/guide-driver/images/btn-next.png
  8. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d1.png
  9. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d2.png
  10. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d3.png
  11. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d4.png
  12. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d5.png
  13. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d6.png
  14. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d7.png
  15. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d8.png
  16. BIN
      src/page-instrument/custom-plugins/guide-driver/images/d9.png
  17. 176 0
      src/page-instrument/custom-plugins/guide-driver/index.less
  18. 0 0
      src/page-instrument/custom-plugins/guide-driver/index.module.less
  19. 263 0
      src/page-instrument/custom-plugins/guide-driver/index.tsx
  20. 58 24
      src/page-instrument/header-top/index.module.less
  21. 209 204
      src/page-instrument/header-top/index.tsx
  22. 81 88
      src/page-instrument/header-top/modeView.tsx
  23. 494 507
      src/page-instrument/view-figner/index.tsx
  24. 57 56
      src/state.ts
  25. 52 50
      src/style.css
  26. 71 90
      src/view/fingering/index.tsx
  27. 314 314
      yarn.lock

+ 1 - 1
osmd-extended

@@ -1 +1 @@
-Subproject commit 331c4861934bbdc6b63c210308fc18f92a80c178
+Subproject commit e96ef0a67e70a21b7ea9a2a5a7925f8a9360a396

+ 16 - 10
package-lock.json

@@ -14,6 +14,7 @@
         "consola": "^2.15.3",
         "cos-js-sdk-v5": "^1.4.20",
         "dayjs": "^1.11.7",
+        "driver.js": "^1.3.1",
         "eventemitter3": "^5.0.0",
         "hammerjs": "^2.0.8",
         "howler": "^2.2.3",
@@ -3449,6 +3450,11 @@
         "tslib": "^2.0.3"
       }
     },
+    "node_modules/driver.js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/driver.js/-/driver.js-1.3.1.tgz",
+      "integrity": "sha512-MvUdXbqSgEsgS/H9KyWb5Rxy0aE6BhOVT4cssi2x2XjmXea6qQfgdx32XKVLLSqTaIw7q/uxU5Xl3NV7+cN6FQ=="
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.342",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.342.tgz",
@@ -7148,8 +7154,7 @@
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
       "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "@octokit/plugin-rest-endpoint-methods": {
       "version": "7.1.2",
@@ -7307,8 +7312,7 @@
     "@vant/use": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/@vant/use/-/use-1.5.1.tgz",
-      "integrity": "sha512-Zxd7lDz/LliVYEQi3PR9a8CQa/kGCVzF0u9hqDMaTlgXlbG0wHMFPllrcG0ThR6bfs8xrYVuSFM9pJn6HSoUGQ==",
-      "requires": {}
+      "integrity": "sha512-Zxd7lDz/LliVYEQi3PR9a8CQa/kGCVzF0u9hqDMaTlgXlbG0wHMFPllrcG0ThR6bfs8xrYVuSFM9pJn6HSoUGQ=="
     },
     "@varlet/icons": {
       "version": "2.9.5",
@@ -7371,8 +7375,7 @@
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz",
       "integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "@vitejs/plugin-vue-jsx": {
       "version": "3.0.1",
@@ -8096,6 +8099,11 @@
         "tslib": "^2.0.3"
       }
     },
+    "driver.js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/driver.js/-/driver.js-1.3.1.tgz",
+      "integrity": "sha512-MvUdXbqSgEsgS/H9KyWb5Rxy0aE6BhOVT4cssi2x2XjmXea6qQfgdx32XKVLLSqTaIw7q/uxU5Xl3NV7+cN6FQ=="
+    },
     "electron-to-chromium": {
       "version": "1.4.342",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.342.tgz",
@@ -8872,8 +8880,7 @@
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/postcss-pxtorem/-/postcss-pxtorem-6.0.0.tgz",
       "integrity": "sha512-ZRXrD7MLLjLk2RNGV6UA4f5Y7gy+a/j1EqjAfp9NdcNYVjUMvg5HTYduTjSkKBkRkfqbg/iKrjMO70V4g1LZeg==",
-      "dev": true,
-      "requires": {}
+      "dev": true
     },
     "proxy-from-env": {
       "version": "1.1.0",
@@ -9682,8 +9689,7 @@
     "ws": {
       "version": "8.13.0",
       "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
-      "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
-      "requires": {}
+      "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA=="
     },
     "y18n": {
       "version": "5.0.8",

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "consola": "^2.15.3",
     "cos-js-sdk-v5": "^1.4.20",
     "dayjs": "^1.11.7",
+    "driver.js": "^1.3.1",
     "eventemitter3": "^5.0.0",
     "hammerjs": "^2.0.8",
     "howler": "^2.2.3",

+ 1 - 0
src/page-instrument/component/mode-type-mode/index.tsx

@@ -36,6 +36,7 @@ export default defineComponent({
         } else {
           // vip
           data.showVip = true;
+          state.isVip = true;
         }
       }
     };

BIN
src/page-instrument/custom-plugins/guide-driver/images/btn-close.png


BIN
src/page-instrument/custom-plugins/guide-driver/images/btn-finsh.png


BIN
src/page-instrument/custom-plugins/guide-driver/images/btn-next.png


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


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


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


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


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


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


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


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


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


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

@@ -0,0 +1,176 @@
+.popoverClass .driver-popover-next-btn {
+  width: 100px;
+  height: 34px;
+  text-shadow: none;
+  border: none;
+  font-weight: 600;
+  font-size: 13px;
+  color: #006ed1;
+  text-align: center;
+  position: absolute;
+  background: url("./images/btn-next.png") no-repeat center transparent;
+  background-size: contain;
+  background-color: transparent !important;
+}
+
+.popoverClass .driver-popover-next-btn:hover,
+.popoverClass .driver-popover-prev-btn:hover {
+  background-color: transparent;
+}
+
+.driver-popover-arrow {
+  display: none;
+}
+
+.driver-popover-close-btn {
+  position: fixed;
+  left: 20px;
+  top: 20px;
+  width: 48px;
+  height: 24px;
+  background: url('./images/btn-close.png') no-repeat center;
+  background-size: contain;
+  color: transparent;
+}
+
+.popoverClass {
+  box-shadow: none;
+  padding: 0;
+}
+
+.popoverClass1 {
+  width: 257px;
+  height: 247px;
+  background: url("./images/d1.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+
+    bottom: 17px;
+    left: 16px;
+  }
+}
+
+.popoverClass2 {
+  width: 264px;
+  height: 228px;
+  background: url("./images/d2.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    bottom: 26px;
+    right: 24px;
+  }
+}
+
+.popoverClass3 {
+  width: 264px;
+  height: 245px;
+  background: url("./images/d3.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 24px;
+    bottom: 23px;
+  }
+}
+
+.popoverClass4 {
+  width: 265px;
+  height: 245px;
+  background: url("./images/d4.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 25px;
+    bottom: 23px;
+  }
+}
+
+.popoverClass5 {
+  width: 264px;
+  height: 245px;
+  background: url("./images/d5.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 24px;
+    bottom: 23px;
+  }
+}
+
+.popoverClass6 {
+  width: 264px;
+  height: 245px;
+  background: url("./images/d6.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 24px;
+    bottom: 23px;
+  }
+}
+
+.popoverClass7 {
+  width: 267px;
+  height: 221px;
+  background: url("./images/d7.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 14px;
+    bottom: 18px;
+  }
+}
+
+.popoverClass8 {
+  width: 277px;
+  height: 152px;
+  background: url("./images/d8.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 24px;
+    bottom: -48px;
+  }
+}
+
+.popoverClass9 {
+  width: 270px;
+  height: 192px;
+  background: url("./images/d9.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-navigation-btns {
+    position: absolute;
+    bottom: -48px;
+    left: 0;
+    right: 0;
+    align-items: center;
+    justify-content: center;
+    flex-direction: row-reverse;
+  }
+
+  .driver-popover-next-btn {
+    position: relative;
+    // right: 14px;
+    // bottom: 18px;
+    width: 82px;
+    height: 33px;
+    background: url('./images/btn-finsh.png') no-repeat center;
+    background-size: contain;
+  }
+
+  .driver-popover-prev-btn {
+    border: 1px solid #fff;
+    border-radius: 100px;
+    color: #fff;
+    background-color: transparent;
+    font-weight: 400;
+    text-shadow: none;
+    width: 82px;
+    height: 33px;
+    text-align: center;
+    margin-left: 14px;
+  }
+}

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


+ 263 - 0
src/page-instrument/custom-plugins/guide-driver/index.tsx

@@ -0,0 +1,263 @@
+import { defineComponent, nextTick, ref } from "vue";
+import { Config, PopoverDOM, State, driver } from "driver.js";
+import "driver.js/dist/driver.css";
+import state from "/src/state";
+import { getGuidance, setGuidance } from "../guide-page/api";
+
+const endGuide = (guideInfo: any) => {
+  try {
+    setGuidance({ guideTag: "guideInfo", guideValue: JSON.stringify(guideInfo) });
+  } catch (e) {
+    console.log(e);
+  }
+};
+
+export const PractiseDriver = defineComponent({
+  name: "practise-driver",
+  setup() {
+    // 初始化部分引导位置
+    const driverInitialPosition = (popover: PopoverDOM, options: { config: Config; state: State }) => {
+      options.config.stageRadius = 5;
+      options.config.stagePadding = 8;
+      try {
+        const rect = options.state.activeElement?.getBoundingClientRect();
+        popover.wrapper.style.marginLeft = (rect?.width || 0) / 2 + 4 + "px";
+      } catch {}
+    };
+
+    const driverObj = driver({
+      showProgress: false,
+      allowClose: false,
+      popoverOffset: 3,
+      // stageRadius: 1000,
+      disableActiveInteraction: true,
+      steps: [
+        {
+          element: ".driver-1",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass1",
+            align: "end",
+            side: "top",
+            nextBtnText: "下一步1/9",
+            showButtons: ["next", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              options.config.stageRadius = 1000;
+              options.config.stagePadding = 0;
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+        {
+          element: ".driver-2",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass2",
+            align: "start",
+            side: "top",
+            nextBtnText: "下一步2/9",
+            showButtons: ["next", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              driverInitialPosition(popover, options);
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+        {
+          element: ".driver-3",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass3",
+            align: "start",
+            side: "top",
+            nextBtnText: "下一步3/9",
+            showButtons: ["next", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              driverInitialPosition(popover, options);
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+        {
+          element: ".driver-4",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass4",
+            align: "start",
+            side: "top",
+            nextBtnText: "下一步4/9",
+            showButtons: ["next", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              driverInitialPosition(popover, options);
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+        {
+          element: ".driver-5",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass5",
+            align: "start",
+            side: "top",
+            nextBtnText: "下一步5/9",
+            showButtons: ["next", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              driverInitialPosition(popover, options);
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+        {
+          element: ".driver-6",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass6",
+            align: "start",
+            side: "top",
+            nextBtnText: "下一步6/9",
+            showButtons: ["next", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              driverInitialPosition(popover, options);
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+        {
+          element: ".driver-7",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass7",
+            align: "start",
+            side: "top",
+            nextBtnText: "下一步7/9",
+            showButtons: ["next", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              driverInitialPosition(popover, options);
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+        {
+          element: ".driver-8",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass8",
+            align: "start",
+            side: "bottom",
+            nextBtnText: "下一步8/9",
+            showButtons: ["next", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              options.config.stageRadius = 1000;
+              options.config.stagePadding = 0;
+              try {
+                const rect = options.state.activeElement?.getBoundingClientRect();
+                popover.wrapper.style.marginLeft = (rect?.width || 0) / 2 - 8 + "px";
+              } catch {}
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+        {
+          element: ".driver-9",
+          popover: {
+            title: "",
+            description: "",
+            popoverClass: "popoverClass popoverClass9",
+            align: "end",
+            side: "bottom",
+            prevBtnText: "在看一遍",
+            doneBtnText: "完成",
+            showButtons: ["next", "previous", "close"],
+            onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
+              options.config.stageRadius = 1000;
+              options.config.stagePadding = 0;
+              try {
+                const rect = options.state.activeElement?.getBoundingClientRect();
+                popover.wrapper.style.marginLeft = -((rect?.width || 0) / 2 - 8) + "px";
+              } catch {}
+            },
+            onPrevClick: (element, step, options) => {
+              driverObj.drive(0);
+            },
+            onNextClick: () => {
+              onDriverClose();
+            },
+            onCloseClick: () => {
+              onDriverClose();
+            },
+          },
+        },
+      ],
+    });
+
+    // watch(
+    //   () => state.audioDone,
+    //   () => {
+    //     driverObj.drive(0);
+    //   }
+    // );
+
+    const guideInfo = ref({} as any);
+    const getAllGuidance = async () => {
+      try {
+        if (state.guideInfo) {
+          guideInfo.value = state.guideInfo;
+        } else {
+          const res = await getGuidance({ guideTag: "guideInfo" });
+          if (res.data) {
+            guideInfo.value = JSON.parse(res.data?.guideValue) || null;
+          } else {
+            guideInfo.value = {};
+          }
+        }
+        if (!(guideInfo.value && guideInfo.value.practiseDriver)) {
+          nextTick(() => {
+            driverObj.drive(7);
+          });
+        }
+      } catch (e) {
+        console.log(e);
+      }
+    };
+
+    getAllGuidance();
+
+    // 结束关闭弹窗
+    const onDriverClose = () => {
+      if (!guideInfo.value) {
+        guideInfo.value = { practiseDriver: true };
+      } else {
+        guideInfo.value.practiseDriver = true;
+      }
+      endGuide(guideInfo.value);
+      driverObj.destroy();
+    };
+
+    return () => <div></div>;
+  },
+});

+ 58 - 24
src/page-instrument/header-top/index.module.less

@@ -5,35 +5,42 @@
     height: 100%;
     flex-shrink: 0;
     padding: 0 30px;
-    background: linear-gradient( 180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%);
+    background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
 }
-.headTopLeftBox{
+
+.headTopLeftBox {
     position: fixed;
     top: 20px;
     left: 30px;
     display: flex;
     align-items: center;
-    .img{
+
+    .img {
         width: 32px;
         height: 32px;
-        &:first-child{
+
+        &:first-child {
             margin-right: 10px;
         }
     }
-    .title{
+
+    .title {
         width: 216px;
-        :global{
-            .van-notice-bar{
+
+        :global {
+            .van-notice-bar {
                 height: 30px;
                 line-height: 30px;
                 padding: 0;
                 font-weight: 600;
                 font-size: 18px;
                 color: #FFFFFF;
-                .van-notice-bar__content{
+
+                .van-notice-bar__content {
                     position: relative;
                     padding-right: 16px;
-                    &::after{
+
+                    &::after {
                         position: absolute;
                         top: 50%;
                         right: 0;
@@ -42,18 +49,20 @@
                         width: 11px;
                         height: 6px;
                         background: url("./image/sj.png") no-repeat;
-                        background-size: 100% 100%;                         
+                        background-size: 100% 100%;
                     }
                 }
             }
         }
     }
+
     .hidenBack {
         opacity: 0;
         pointer-events: none;
     }
 }
-.modeChangeBox{
+
+.modeChangeBox {
     position: fixed;
     top: 20px;
     right: 30px;
@@ -63,35 +72,47 @@
     display: flex;
     align-items: center;
     padding: 0 10px;
-    .img{
+
+    .img {
         width: 18px;
         height: 18px;
     }
-    .title{
+
+    .title {
         margin-left: 6px;
         font-weight: 500;
         font-size: 14px;
         color: #FFFFFF;
     }
 }
+
 .headRight {
     display: flex;
     align-items: center;
     height: 100%;
+
     .btn {
         position: relative;
         display: flex;
         flex-direction: column;
         align-items: center;
         cursor: pointer;
-        margin-right: 30px;
-        &:last-child{
+        padding: 0 8px;
+        margin-right: 14px;
+
+        &:first-child {
+            margin-left: -8px;
+        }
+
+        &:last-child {
             margin-right: 0;
         }
+
         .iconBtn {
             width: 24px;
             height: 24px;
         }
+
         span {
             margin-top: 3px;
             font-weight: 500;
@@ -100,9 +121,11 @@
             line-height: 17px;
         }
     }
-    .metronomeBtn{
+
+    .metronomeBtn {
         position: relative;
-        .speedCon{
+
+        .speedCon {
             transform: scale(0.83);
             transform-origin: left bottom;
             padding: 2px;
@@ -114,11 +137,13 @@
             background: #FFC121;
             border-radius: 120px 120px 120px 1px;
             border: 1px solid #FFFFFF;
-            >img{
+
+            >img {
                 width: 15px;
                 height: 11px;
             }
-            >div{
+
+            >div {
                 margin-left: 1px;
                 font-weight: 600;
                 font-size: 12px;
@@ -139,6 +164,7 @@
     right: 30px;
     bottom: 12px;
     transition: bottom .2s ease;
+
     .btnWrap {
         width: 50px;
         height: 50px;
@@ -149,6 +175,7 @@
             height: 100%;
         }
     }
+
     &.playLeftButton {
         left: 46px !important;
         right: auto !important;
@@ -160,7 +187,7 @@
         left: auto !important;
         bottom: 12px !important;
     }
-    
+
     .progress {
         position: absolute;
         left: 50%;
@@ -222,11 +249,13 @@
     background: url(./image/bg.png) no-repeat;
     background-size: 100% 100%;
     transition: all .3s;
-    &.hidden{
+
+    &.hidden {
         opacity: 0;
         transform: translateY(100%);
         pointer-events: none;
     }
+
     .back {
         position: absolute;
         width: 38px;
@@ -235,6 +264,7 @@
         top: 17px;
         cursor: pointer;
     }
+
     .name {
         position: absolute;
         left: 50%;
@@ -243,19 +273,23 @@
         width: 87px;
         height: 21px;
     }
+
     .modeBox {
         width: 100%;
         margin-top: 90px;
         display: flex;
         justify-content: space-between;
         padding: 0 36px;
-        &.twoModeBox{
+
+        &.twoModeBox {
             justify-content: center;
-            > img + img{
+
+            >img+img {
                 margin-left: 150px;
             }
         }
-        > img {
+
+        >img {
             width: calc((100% - 2*40px)/3);
             max-width: 220px;
         }

+ 209 - 204
src/page-instrument/header-top/index.tsx

@@ -1,4 +1,4 @@
-import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef,ComputedRef } from "vue";
+import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef, ComputedRef } from "vue";
 import styles from "./index.module.less";
 
 import iconBack from "./image/icon-back.png";
@@ -28,9 +28,10 @@ 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 { smoothAnimationState } from "../view-detail/smoothAnimation"
+import ModeView from "./modeView";
+import { smoothAnimationState } from "../view-detail/smoothAnimation";
 import { isMusicList, musicListShow } from "../component/the-music-list";
+import { PractiseDriver } from "../custom-plugins/guide-driver";
 
 /** 头部数据和方法 */
 export const headTopData = reactive({
@@ -57,7 +58,7 @@ export const headTopData = reactive({
       // 如果延迟检测资源还在加载中,给出提示
       if (!evaluatingData.jsonLoadDone) {
         evaluatingData.jsonLoading = true;
-        state.audioDone && showToast("资源加载中,请稍后");  //音频资源加载完之后才提示
+        state.audioDone && showToast("资源加载中,请稍后"); //音频资源加载完之后才提示
         return;
       }
       // 如果是pc端, 评测模式暂不可用
@@ -72,8 +73,8 @@ export const headTopData = reactive({
       }
       // 评测模式,只有一行谱模式
       if (!state.isSingleLine) {
-        state.isSingleLine = true
-        refreshMusicSvg()
+        state.isSingleLine = true;
+        refreshMusicSvg();
       }
       state.playIngSpeed = state.originSpeed;
       handleStartEvaluat();
@@ -84,8 +85,8 @@ export const headTopData = reactive({
     } else if (value === "follow") {
       // 跟练模式,只有一行谱模式
       if (!state.isSingleLine) {
-        state.isSingleLine = true
-        refreshMusicSvg()
+        state.isSingleLine = true;
+        refreshMusicSvg();
       }
       smoothAnimationState.isShow.value = false;
       toggleFollow();
@@ -99,143 +100,143 @@ export const headData = reactive({
   musicTypeShow: false,
 });
 
-let resetBtn:ComputedRef<{
+let resetBtn: ComputedRef<{
   display: boolean;
   disabled: boolean;
-}>
+}>;
 /**
  * 处理模式切换
  * @param oldPlayType   没改变之前的播放模式
  * @param oldPlaySource  没改变之前的播放类型
  * @param isforceReset   是否强制刷新播放状态 模式times时值改变时候也刷新
  */
-export function handlerModeChange(oldPlayType:"play"|"sing", oldPlaySource:IPlayState,isforceReset?:boolean) {
-    const isModeChange = modeChangeHandleTimes(oldPlayType, oldPlaySource)
-    // 没有切换的时候 不处理下面的
-    if(isModeChange){
-      try {
-        metronomeData.metro.calculation(state.times);
-      } catch (error) {}
-      console.log("重新之后的times", state.times, state.fixtime)
-    }
-    if(isModeChange||isforceReset){
-      // 重置播放状态
-      handleRessetState()
-      // 隐藏重播按钮
-      resetBtn && (resetBtn.value.display = false)
-    }
+export function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: IPlayState, isforceReset?: boolean) {
+  const isModeChange = modeChangeHandleTimes(oldPlayType, oldPlaySource);
+  // 没有切换的时候 不处理下面的
+  if (isModeChange) {
+    try {
+      metronomeData.metro.calculation(state.times);
+    } catch (error) {}
+    console.log("重新之后的times", state.times, state.fixtime);
+  }
+  if (isModeChange || isforceReset) {
+    // 重置播放状态
+    handleRessetState();
+    // 隐藏重播按钮
+    resetBtn && (resetBtn.value.display = false);
+  }
 }
 // 模式切换之后重新给times赋值
-function modeChangeHandleTimes(oldPlayType:"play"|"sing", oldPlaySource:IPlayState){
-  const playType = state.playType
-  const playSource = state.playSource
-  const {notBeatFixtime, xmlMp3BeatFixTime, difftime} = state.times[0]
-  const { isOpenMetronome, isSingOpenMetronome } = state 
+function modeChangeHandleTimes(oldPlayType: "play" | "sing", oldPlaySource: IPlayState) {
+  const playType = state.playType;
+  const playSource = state.playSource;
+  const { notBeatFixtime, xmlMp3BeatFixTime, difftime } = state.times[0];
+  const { isOpenMetronome, isSingOpenMetronome } = state;
   // 演奏向演唱切
-  if(oldPlayType === "play"&&playType === "sing"){
-    if(playSource === "mingSong"){
+  if (oldPlayType === "play" && playType === "sing") {
+    if (playSource === "mingSong") {
       // 唱名文件也要加上弱起时间  他们制作曲子加了弱起时间
-      state.fixtime = difftime
-      state.times.map(item => {
-        item.time = item.xmlNoteTime + difftime
-        item.endtime = item.xmlNoteEndTime + difftime
-        item.fixtime = difftime
-      })
-      return true
-    }else{
+      state.fixtime = difftime;
+      state.times.map((item) => {
+        item.time = item.xmlNoteTime + difftime;
+        item.endtime = item.xmlNoteEndTime + difftime;
+        item.fixtime = difftime;
+      });
+      return true;
+    } else {
       //演奏开了节拍器,演唱没开节拍器
-      if(isOpenMetronome&&!isSingOpenMetronome){
-        state.fixtime = notBeatFixtime
-        state.times.map(item => {
-          item.time = item.notBeatTime
-          item.endtime = item.notBeatEndTime
-          item.fixtime = notBeatFixtime
-        })
-        return true
-      }else if(!isOpenMetronome&&isSingOpenMetronome){
-        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
-        state.times.map(item => {
-          item.time = item.notBeatTime + xmlMp3BeatFixTime
-          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
-          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
-        })
-        return true
+      if (isOpenMetronome && !isSingOpenMetronome) {
+        state.fixtime = notBeatFixtime;
+        state.times.map((item) => {
+          item.time = item.notBeatTime;
+          item.endtime = item.notBeatEndTime;
+          item.fixtime = notBeatFixtime;
+        });
+        return true;
+      } else if (!isOpenMetronome && isSingOpenMetronome) {
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
+        state.times.map((item) => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime;
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime;
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
+        });
+        return true;
       }
     }
-  }else if(oldPlayType === "sing"&&playType === "play"){
+  } else if (oldPlayType === "sing" && playType === "play") {
     // 演唱向演奏切
-    if(oldPlaySource === "mingSong"){
+    if (oldPlaySource === "mingSong") {
       // 有节拍器
-      if(isOpenMetronome){
-        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
-        state.times.map(item => {
-          item.time = item.notBeatTime + xmlMp3BeatFixTime
-          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
-          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
-        })
-        return true
-      }else{
-        state.fixtime = notBeatFixtime
-        state.times.map(item => {
-          item.time = item.notBeatTime
-          item.endtime = item.notBeatEndTime
-          item.fixtime = notBeatFixtime
-        })
-        return true
+      if (isOpenMetronome) {
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
+        state.times.map((item) => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime;
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime;
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
+        });
+        return true;
+      } else {
+        state.fixtime = notBeatFixtime;
+        state.times.map((item) => {
+          item.time = item.notBeatTime;
+          item.endtime = item.notBeatEndTime;
+          item.fixtime = notBeatFixtime;
+        });
+        return true;
       }
     }
     // 演奏开了节拍器,演唱没开节拍器
-    if(isOpenMetronome&&!isSingOpenMetronome){
-      state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
-      state.times.map(item => {
-        item.time = item.notBeatTime + xmlMp3BeatFixTime
-        item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
-        item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
-      })
-      return true
-    }else if(!isOpenMetronome&&isSingOpenMetronome){
-      state.fixtime = notBeatFixtime
-      state.times.map(item => {
-        item.time = item.notBeatTime
-        item.endtime = item.notBeatEndTime
-        item.fixtime = notBeatFixtime
-      })
-      return true
+    if (isOpenMetronome && !isSingOpenMetronome) {
+      state.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
+      state.times.map((item) => {
+        item.time = item.notBeatTime + xmlMp3BeatFixTime;
+        item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime;
+        item.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
+      });
+      return true;
+    } else if (!isOpenMetronome && isSingOpenMetronome) {
+      state.fixtime = notBeatFixtime;
+      state.times.map((item) => {
+        item.time = item.notBeatTime;
+        item.endtime = item.notBeatEndTime;
+        item.fixtime = notBeatFixtime;
+      });
+      return true;
     }
-  }else if(oldPlayType === "sing"&&playType === "sing"){
-    // 演唱之间切换  
+  } else if (oldPlayType === "sing" && playType === "sing") {
+    // 演唱之间切换
     // 切到唱名时候
-    if(playSource === "mingSong"){
+    if (playSource === "mingSong") {
       // 唱名文件也要加上弱起时间  他们制作曲子加了弱起时间
-      state.fixtime = difftime
-      state.times.map(item => {
-        item.time = item.xmlNoteTime + difftime
-        item.endtime = item.xmlNoteEndTime + difftime
-        item.fixtime = difftime
-      })
-      return true
-    }else if(oldPlaySource === "mingSong"){
+      state.fixtime = difftime;
+      state.times.map((item) => {
+        item.time = item.xmlNoteTime + difftime;
+        item.endtime = item.xmlNoteEndTime + difftime;
+        item.fixtime = difftime;
+      });
+      return true;
+    } else if (oldPlaySource === "mingSong") {
       // 有节拍器
-      if(isSingOpenMetronome){
-        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
-        state.times.map(item => {
-          item.time = item.notBeatTime + xmlMp3BeatFixTime
-          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
-          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
-        })
-        return true
-      }else{
-        state.fixtime = notBeatFixtime
-        state.times.map(item => {
-          item.time = item.notBeatTime
-          item.endtime = item.notBeatEndTime
-          item.fixtime = notBeatFixtime
-        })
-        return true
+      if (isSingOpenMetronome) {
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
+        state.times.map((item) => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime;
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime;
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
+        });
+        return true;
+      } else {
+        state.fixtime = notBeatFixtime;
+        state.times.map((item) => {
+          item.time = item.notBeatTime;
+          item.endtime = item.notBeatEndTime;
+          item.fixtime = notBeatFixtime;
+        });
+        return true;
       }
     }
   }
-  return false
+  return false;
 }
 
 export default defineComponent({
@@ -345,20 +346,20 @@ export default defineComponent({
       // 评测开始 禁用
       if (state.modeType === "evaluating") return { display: false, disabled: true };
       if (!state.isAppPlay) {
-        if(state.playType === "play"){
-            // 原声, 伴奏 少一个,就不能切换
-            if (state.music && state.accompany) return { display: true, disabled: false };
+        if (state.playType === "play") {
+          // 原声, 伴奏 少一个,就不能切换
+          if (state.music && state.accompany) return { display: true, disabled: false };
         } else {
           // 播放过程中不能切换
-          if (state.playState === "play"){
+          if (state.playState === "play") {
             return { display: true, disabled: true };
           }
-          // 范唱 
-          let index = 0
-          state.fanSong && index++
-          state.banSong && index++
-          state.mingSong && index++
-          if(index > 1) {
+          // 范唱
+          let index = 0;
+          state.fanSong && index++;
+          state.banSong && index++;
+          state.mingSong && index++;
+          if (index > 1) {
             return { display: true, disabled: false };
           }
         }
@@ -377,23 +378,23 @@ export default defineComponent({
       // 音频播放中 禁用
       if (state.playState === "play") return { display: true, disabled: true };
       if (!state.isAppPlay) {
-          let index = 0
-          state.music && index++
-          state.accompany && index++
-          let songIndex = 0
-          state.fanSong && songIndex++
-          state.banSong && songIndex++
-          state.mingSong && songIndex++
-          // 演唱和演奏 都有数据的时间不禁用
-          if(songIndex>0&&index>0) {
-            return { display: true, disabled: false };
-          }
+        let index = 0;
+        state.music && index++;
+        state.accompany && index++;
+        let songIndex = 0;
+        state.fanSong && songIndex++;
+        state.banSong && songIndex++;
+        state.mingSong && songIndex++;
+        // 演唱和演奏 都有数据的时间不禁用
+        if (songIndex > 0 && index > 0) {
+          return { display: true, disabled: false };
+        }
       }
       return {
         disabled: true,
         display: true,
       };
-    })
+    });
     /** 模式切换按钮 */
     const toggleBtn = computed(() => {
       // 选择模式, url设置模式 不显示
@@ -603,38 +604,41 @@ export default defineComponent({
         >
           {/* 返回和标题 */}
           <div class={styles.headTopLeftBox}>
-            <img src={iconBack} class={['headTopBackBtn', styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
-            {
-              state.modeType === "practise" && smoothAnimationState.isShow.value ? 
-                <div class={styles.title} onClick={()=>{
-                  isMusicList.value && (musicListShow.value = true)
-                }}>
-                  <NoticeBar
-                    text={state.examSongName}
-                    background="none"
-                  />
-              </div> :
-              <img src={listImg} class={[styles.img]} onClick={()=>{
-                  isMusicList.value && (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]} 
+            <img src={iconBack} class={["headTopBackBtn", styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
+            {state.modeType === "practise" && smoothAnimationState.isShow.value ? (
+              <div
+                class={[styles.title, "driver-8"]}
                 onClick={() => {
-                    handleRessetState();
-                    headTopData.modeType = "init";
+                  isMusicList.value && (musicListShow.value = true);
                 }}
               >
-                <img class={styles.img} src={iconMode} />
-                <div class={styles.title}>{state.modeType==="practise" ? '练习模式' : state.modeType==="follow" ? "跟练模式" : state.modeType==="evaluating" ? "评测模式" : ""}</div>
+                <NoticeBar text={state.examSongName} background="none" />
               </div>
-          }
+            ) : (
+              <img
+                src={listImg}
+                class={[styles.img, "driver-8"]}
+                onClick={() => {
+                  isMusicList.value && (musicListShow.value = true);
+                }}
+              />
+            )}
+          </div>
+          {/* 模式切换 */}
+          {state.playType === "play" && (
+            <div
+              id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
+              style={{ display: toggleBtn.value.display ? "" : "none" }}
+              class={["driver-9", styles.modeChangeBox, toggleBtn.value.disabled && styles.disabled]}
+              onClick={() => {
+                handleRessetState();
+                headTopData.modeType = "init";
+              }}
+            >
+              <img class={styles.img} src={iconMode} />
+              <div class={styles.title}>{state.modeType === "practise" ? "练习模式" : state.modeType === "follow" ? "跟练模式" : state.modeType === "evaluating" ? "评测模式" : ""}</div>
+            </div>
+          )}
           {/* 功能按钮 */}
           <div
             class={[styles.headRight]}
@@ -671,18 +675,18 @@ export default defineComponent({
             ) : null} */}
             <div
               style={{ display: playTypeBtn.value.display ? "" : "none" }}
-              class={[styles.btn, playTypeBtn.value.disabled && styles.disabled]}
+              class={["driver-2", styles.btn, playTypeBtn.value.disabled && styles.disabled]}
               onClick={() => {
-                const oldPlayType = state.playType
-                const oldPlaySource = state.playSource
-                if(state.playType === "play"){
-                  state.playType = "sing"
-                  state.playSource = state.fanSong?"music":state.banSong?"background":"mingSong"
+                const oldPlayType = state.playType;
+                const oldPlaySource = state.playSource;
+                if (state.playType === "play") {
+                  state.playType = "sing";
+                  state.playSource = state.fanSong ? "music" : state.banSong ? "background" : "mingSong";
                 } else {
-                  state.playType = "play"
-                  state.playSource = state.music?"music":"background"
+                  state.playType = "play";
+                  state.playSource = state.music ? "music" : "background";
                 }
-                handlerModeChange(oldPlayType, oldPlaySource, true)
+                handlerModeChange(oldPlayType, oldPlaySource, true);
               }}
             >
               <img style={{ display: state.playType === "play" ? "" : "none" }} class={styles.iconBtn} src={headImg(`perform.png`)} />
@@ -692,30 +696,30 @@ export default defineComponent({
             <div
               id={state.platform === IPlatform.PC ? "teacherTop-1" : "studnetT-1"}
               style={{ display: originBtn.value.display ? "" : "none" }}
-              class={[styles.btn, originBtn.value.disabled && styles.disabled]}
+              class={["driver-3", styles.btn, originBtn.value.disabled && styles.disabled]}
               onClick={() => {
-                const oldPlayType = state.playType
-                const oldPlaySource = state.playSource
-                if(state.playType === 'play'){
+                const oldPlayType = state.playType;
+                const oldPlaySource = state.playSource;
+                if (state.playType === "play") {
                   state.playSource = state.playSource === "music" ? "background" : "music";
-                }else{
-                  if(state.playSource === "music"){
-                    state.playSource = state.banSong ? "background" :"mingSong"
-                  }else if(state.playSource === "background"){
-                    state.playSource = state.mingSong ? "mingSong" :"music"
-                  }else {
-                    state.playSource = state.fanSong ? "music" :"background"
+                } else {
+                  if (state.playSource === "music") {
+                    state.playSource = state.banSong ? "background" : "mingSong";
+                  } else if (state.playSource === "background") {
+                    state.playSource = state.mingSong ? "mingSong" : "music";
+                  } else {
+                    state.playSource = state.fanSong ? "music" : "background";
                   }
                 }
-                handlerModeChange(oldPlayType, oldPlaySource)
+                handlerModeChange(oldPlayType, oldPlaySource);
               }}
             >
-              <img style={{ display: state.playSource === "music" ? "" : "none" }} class={styles.iconBtn} src={state.playType === 'play'?headImg(`music.png`):headImg(`music1.png`)} />
-              <img style={{ display: state.playSource === "background" ? "" : "none" }} class={styles.iconBtn} src={state.playType === 'play'?headImg(`background.png`):headImg(`background1.png`)} />
+              <img style={{ display: state.playSource === "music" ? "" : "none" }} class={styles.iconBtn} src={state.playType === "play" ? headImg(`music.png`) : headImg(`music1.png`)} />
+              <img style={{ display: state.playSource === "background" ? "" : "none" }} class={styles.iconBtn} src={state.playType === "play" ? headImg(`background.png`) : headImg(`background1.png`)} />
               <img style={{ display: state.playSource === "mingSong" ? "" : "none" }} class={styles.iconBtn} src={headImg(`mingsong.png`)} />
-              <span>{state.playSource === "music" ? (state.playType ==="play" ? "原声" : "范唱") : state.playSource === "background" ? (state.playType ==="play" ? "伴奏" : "伴唱") : "唱名"}</span>
+              <span>{state.playSource === "music" ? (state.playType === "play" ? "原声" : "范唱") : state.playSource === "background" ? (state.playType === "play" ? "伴奏" : "伴唱") : "唱名"}</span>
             </div>
-            <div id={state.platform === IPlatform.PC ? "teacherTop-2" : "studnetT-2"} style={{ display: selectBtn.value.display ? "" : "none" }} class={[styles.btn, selectBtn.value.disabled && styles.disabled]} onClick={() => handleChangeSection()}>
+            <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]} onClick={() => handleChangeSection()}>
               <img style={{ display: state.section.length === 0 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section0.png`)} />
               <img style={{ display: state.section.length === 1 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section1.png`)} />
               <img style={{ display: state.section.length === 2 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section2.png`)} />
@@ -725,7 +729,7 @@ export default defineComponent({
               <>
                 <div
                   style={{ display: metronomeBtn.value.display ? "" : "none" }}
-                  class={[styles.btn, styles.metronomeBtn, metronomeBtn.value.disabled && styles.disabled]}
+                  class={["driver-5", styles.btn, styles.metronomeBtn, metronomeBtn.value.disabled && styles.disabled]}
                   onClick={async () => {
                     headData.speedShow = !headData.speedShow;
                   }}
@@ -737,14 +741,14 @@ export default defineComponent({
                     <img src={headImg("speed.png")} />
                     <div>{state.speed}</div>
                   </div>
-                </div>          
+                </div>
                 {
-                  <Popup v-model:show={headData.speedShow} class="popup-custom van-scale center-closeBtn settingBoxClass_drag" transition="van-scale" teleport="body" style={positionInfo.styleDrag.value} overlay-style={{background:'rgba(0, 0, 0, 0.3)'}}>
+                  <Popup v-model:show={headData.speedShow} class="popup-custom van-scale center-closeBtn settingBoxClass_drag" transition="van-scale" teleport="body" style={positionInfo.styleDrag.value} overlay-style={{ background: "rgba(0, 0, 0, 0.3)" }}>
                     <Speed />
                     {state.platform === IPlatform.PC && <Dragbom showGuide={!state.guideInfo?.teacherDrag} onGuideDone={handleGuide} />}
                   </Popup>
                 }
-              </>             
+              </>
             )}
             {/* {state.enableNotation ? (
               <Popover trigger="manual" v-model:show={headData.musicTypeShow} class={state.platform === IPlatform.PC && styles.pcTransPop} placement={state.platform === IPlatform.PC ? "top-end" : "bottom-end"} overlay={false} offset={state.platform === IPlatform.PC ? [0, 40] : [0, 8]}>
@@ -778,7 +782,7 @@ export default defineComponent({
                 <span>声轨</span>
               </div>
             )}
-            <div id={state.platform === IPlatform.PC ? "teacherTop-6" : "studnetT-6"} style={{ display: settingBtn.value.display ? "" : "none" }} class={[styles.btn, settingBtn.value.disabled && styles.disabled]} onClick={() => (headTopData.settingMode = true)}>
+            <div id={state.platform === IPlatform.PC ? "teacherTop-6" : "studnetT-6"} style={{ display: settingBtn.value.display ? "" : "none" }} class={["driver-6", styles.btn, settingBtn.value.disabled && styles.disabled]} onClick={() => (headTopData.settingMode = true)}>
               <img class={styles.iconBtn} src={headImg("icon_menu.png")} />
               <span>设置</span>
             </div>
@@ -790,6 +794,8 @@ export default defineComponent({
           id="studnetT-7"
           style={{ display: playBtn.value.display ? "" : "none" }}
           class={[
+            // 引导使用的类
+            "driver-1",
             styles.playBtn,
             playBtn.value.disabled && styles.disabled,
             state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.playLeftButton : state.platform === IPlatform.PC && state.musicScoreBtnDirection === "right" ? styles.playRightButton : "",
@@ -807,17 +813,13 @@ export default defineComponent({
         <div
           id="tips-step-9"
           style={{ display: resetBtn.value.display ? "" : "none" }}
-          class={[
-            styles.resetBtn,
-            resetBtn.value.disabled && styles.disabled,
-            state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.pauseLeftButton : state.platform === IPlatform.PC && state.musicScoreBtnDirection === "right" ? styles.pauseRightButton : "",
-          ]}
+          class={[styles.resetBtn, resetBtn.value.disabled && styles.disabled, state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.pauseLeftButton : state.platform === IPlatform.PC && state.musicScoreBtnDirection === "right" ? styles.pauseRightButton : ""]}
           onClick={() => handleResetPlay()}
         >
           <img class={styles.iconBtn} src={headImg("icon_reset.png")} />
         </div>
 
-        <Popup v-model:show={headTopData.settingMode} class="popup-custom van-scale center-closeBtn settingBoxClass_drag" transition="van-scale" teleport="body" style={positionInfo.styleDrag.value} overlay-style={{background:'rgba(0, 0, 0, 0.3)'}}>
+        <Popup v-model:show={headTopData.settingMode} class="popup-custom van-scale center-closeBtn settingBoxClass_drag" transition="van-scale" teleport="body" style={positionInfo.styleDrag.value} overlay-style={{ background: "rgba(0, 0, 0, 0.3)" }}>
           <Settting />
           {state.platform === IPlatform.PC && <Dragbom showGuide={!state.guideInfo?.teacherDrag} onGuideDone={handleGuide} />}
         </Popup>
@@ -829,6 +831,9 @@ export default defineComponent({
         {/* isAllBtns */}
         {isAllBtns.value && !query.isCbs && showGuideIndex.value && <TeacherTop></TeacherTop>}
         {isAllBtnsStudent.value && !query.isCbs && showGuideIndex.value && <StudentTop></StudentTop>}
+
+        {/* 练习模式功能引导 加载音频完成 不是会员 */}
+        {state.modeType === "practise" && state.audioDone && !state.isVip && <PractiseDriver />}
       </>
     );
   },

+ 81 - 88
src/page-instrument/header-top/modeView.tsx

@@ -1,93 +1,86 @@
-import { defineComponent, onMounted, watch, reactive } from "vue"
-import styles from "./index.module.less"
-import backImg from "./image/back.png"
-import nameImg from "./image/zt.png"
-import lxImg from "./image/lx.png"
-import glImg from "./image/gl.png"
-import pcImg from "./image/pc.png"
-import { headTopData } from "./index"
-import TheVip from "../custom-plugins/the-vip"
-import { getQuery } from "/src/utils/queryString"
-import { storeData } from "/src/store"
-import state from "/src/state"
-import { studentQueryUserInfo } from "../api"
-import { usePageVisibility } from "@vant/use"
+import { defineComponent, onMounted, watch, reactive } from "vue";
+import styles from "./index.module.less";
+import backImg from "./image/back.png";
+import nameImg from "./image/zt.png";
+import lxImg from "./image/lx.png";
+import glImg from "./image/gl.png";
+import pcImg from "./image/pc.png";
+import { headTopData } from "./index";
+import TheVip from "../custom-plugins/the-vip";
+import { getQuery } from "/src/utils/queryString";
+import { storeData } from "/src/store";
+import state from "/src/state";
+import { studentQueryUserInfo } from "../api";
+import { usePageVisibility } from "@vant/use";
 
 export default defineComponent({
-   name: "modeView",
-   setup() {
-      const query = getQuery()
-      const data = reactive({
-         showPC: false,
-         showStudent: false,
-         showVip: false
-      })
-      const openGuid = () => {
-         // 加载后 判断 端口号 加载对应的引导
-         if (storeData.platformType !== "STUDENT" || storeData.user.clientType !== "STUDENT") {
-            // PC
-            data.showPC = true
-         } else {
-            // 从课堂乐器学生端课件预览默认不显示会员
-            if (storeData.user.vipMember || state.paymentType === "FREE" || query.showCourseMember === "true") {
-               // 学生端
-               data.showStudent = true
-            } else {
-               // vip
-               data.showVip = true
-            }
-         }
+  name: "modeView",
+  setup() {
+    const query = getQuery();
+    const data = reactive({
+      showPC: false,
+      showStudent: false,
+      showVip: false,
+    });
+    const openGuid = () => {
+      // 加载后 判断 端口号 加载对应的引导
+      if (storeData.platformType !== "STUDENT" || storeData.user.clientType !== "STUDENT") {
+        // PC
+        data.showPC = true;
+      } else {
+        // 从课堂乐器学生端课件预览默认不显示会员
+        if (storeData.user.vipMember || state.paymentType === "FREE" || query.showCourseMember === "true") {
+          // 学生端
+          data.showStudent = true;
+        } else {
+          // vip
+          data.showVip = true;
+          state.isVip = true;
+        }
       }
+    };
 
-      const getUserInfo = async () => {
-         const res = await studentQueryUserInfo()
-         const student = res?.data || {}
-         storeData.user.vipMember = student.vipMember
-         // console.log("🚀 ~ student:", student);
-         if (storeData.user.vipMember) {
-            data.showVip = false
-            openGuid()
-         }
+    const getUserInfo = async () => {
+      const res = await studentQueryUserInfo();
+      const student = res?.data || {};
+      storeData.user.vipMember = student.vipMember;
+      // console.log("🚀 ~ student:", student);
+      if (storeData.user.vipMember) {
+        data.showVip = false;
+        openGuid();
       }
-      const pageVisible = usePageVisibility()
-      watch(
-         () => pageVisible.value,
-         val => {
-            if (val === "visible") {
-               if (storeData.user.vipMember) return
-               console.log("页面显示")
-               getUserInfo()
-            }
-         }
-      )
-      onMounted(() => {
-         openGuid()
-      })
-      return () => (
-         <div class={[styles.modeView, headTopData.modeType !== "init" && styles.hidden]}>
-            <img
-               src={backImg}
-               class={styles.back}
-               onClick={() => {
-                  headTopData.modeType = "show"
-               }}
-            />
-            <img src={nameImg} class={styles.name} />
-            <div
-               class={[
-                  styles.modeBox,
-                  ((!state.isPercussion && !state.enableEvaluation) ||
-                     (state.isPercussion && state.enableEvaluation) ||
-                     (state.isPercussion && !state.enableEvaluation)) &&
-                     styles.twoModeBox
-               ]}
-            >
-               <img src={lxImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("practise")} />
-               {!state.isPercussion && <img src={glImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("follow")} />}
-               {state.enableEvaluation && <img src={pcImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("evaluating")} />}
-            </div>
-            {data.showVip && <TheVip />}
-         </div>
-      )
-   }
-})
+    };
+    const pageVisible = usePageVisibility();
+    watch(
+      () => pageVisible.value,
+      (val) => {
+        if (val === "visible") {
+          if (storeData.user.vipMember) return;
+          console.log("页面显示");
+          getUserInfo();
+        }
+      }
+    );
+    onMounted(() => {
+      openGuid();
+    });
+    return () => (
+      <div class={[styles.modeView, headTopData.modeType !== "init" && styles.hidden]}>
+        <img
+          src={backImg}
+          class={styles.back}
+          onClick={() => {
+            headTopData.modeType = "show";
+          }}
+        />
+        <img src={nameImg} class={styles.name} />
+        <div class={[styles.modeBox, ((!state.isPercussion && !state.enableEvaluation) || (state.isPercussion && state.enableEvaluation) || (state.isPercussion && !state.enableEvaluation)) && styles.twoModeBox]}>
+          <img src={lxImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("practise")} />
+          {!state.isPercussion && <img src={glImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("follow")} />}
+          {state.enableEvaluation && <img src={pcImg} class={styles.modeImg} onClick={() => headTopData.handleChangeModeType("evaluating")} />}
+        </div>
+        {data.showVip && <TheVip />}
+      </div>
+    );
+  },
+});

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 494 - 507
src/page-instrument/view-figner/index.tsx


+ 57 - 56
src/state.ts

@@ -15,7 +15,7 @@ import { getMusicSheetDetail } from "./utils/baseApi"
 import { getQuery } from "/src/utils/queryString";
 import { followData, skipNotePractice } from "/src/view/follow-practice/index"
 import { changeSongSourceByBate } from "/src/view/audio-list"
-import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTime, moveTranslateXNum, destroySmoothAnimation} from "/src/page-instrument/view-detail/smoothAnimation"
+import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTime, moveTranslateXNum, destroySmoothAnimation } from "/src/page-instrument/view-detail/smoothAnimation"
 import { storeData } from "/src/store";
 import { downloadXmlStr } from "./view/music-score"
 import { musicScoreRef } from "/src/page-instrument/view-detail/index"
@@ -268,6 +268,7 @@ const state = reactive({
   /** 声部ID */
   subjectId: 0 as number,
   trackId: 0 as string | number,
+  isVip: false, // 是否会员
   /** 分类ID */
   categoriesId: 0,
   /** 分类名称 */
@@ -289,7 +290,7 @@ const state = reactive({
   /** 扩展样式字段 */
   extStyleConfigJson: {} as any,
   /** 是否开启节拍器(mp3节拍器) */
-  isOpenMetronome: false,  
+  isOpenMetronome: false,
   /** 演唱模式是否开启节拍器(mp3节拍器) */
   isSingOpenMetronome: false,
   /** 是否显示指法 */
@@ -303,7 +304,7 @@ const state = reactive({
   /** 伴唱 */
   banSong: "",
   /** 唱名 */
-  mingSong: "", 
+  mingSong: "",
   /** 节拍器音乐资源 */
   beatSong: {
     music: "",
@@ -333,7 +334,7 @@ const state = reactive({
   /** 当前显示声部索引 */
   partIndex: 0,
   /** 演奏是否需要节拍器 */
-  needTick: false, 
+  needTick: false,
   /** 演唱模式是否需要节拍器 */
   needSingTick: false,
   /** 曲谱实例 */
@@ -536,7 +537,7 @@ const setStep = () => {
 };
 /** 开始播放 */
 export const onPlay = () => {
-  console.log("开始播放",'音频总时长:',getAudioDuration());
+  console.log("开始播放", '音频总时长:', getAudioDuration());
   state.playEnd = false;
   // offset_duration = browserInfo.xiaomi ? 0.2 : 0.08;
   offset_duration = 0.2;
@@ -565,7 +566,7 @@ export const onEnded = () => {
   if (state.playEnd) {
     console.log('音频播放结束,无需再次执行')
     return
-  }  
+  }
   // 修改状态为结束
   state.playEnd = true;
   state.playState = "paused";
@@ -697,7 +698,7 @@ export const togglePlay = async (playState?: "play" | "paused", sourceType?: str
     return
   }
   // 播放之前  当为评测模式和不为MIDI时候按  是否禁用节拍器  切换音源
-  if ((playState ? playState : state.playState === "paused" ? "play" : "paused") ==='play' && state.modeType === "practise" && state.playMode !== "MIDI") {
+  if ((playState ? playState : state.playState === "paused" ? "play" : "paused") === 'play' && state.modeType === "practise" && state.playMode !== "MIDI") {
     console.log("设置音源")
     changeSongSourceByBate(metronomeData.disable)
   }
@@ -725,14 +726,14 @@ export const togglePlay = async (playState?: "play" | "paused", sourceType?: str
   if (state.playState === "play" && state.sectionStatus && state.section.length == 2 && state.playProgress === 0) {
     resetPlaybackToStart();
   }
-   // 当在节拍器播放期间暂停的话 就暂停节拍器
-  if(state.playState === "paused") {
+  // 当在节拍器播放期间暂停的话 就暂停节拍器
+  if (state.playState === "paused") {
     closeTick()
   }
   // 设置为开始播放时, 如果需要节拍,先播放节拍器   只有在当前播放时间不为0的时候开启节拍器
-  if (state.playState === "play" && getAudioCurrentTime()===0 && ((state.playType==="play"&&state.needTick)||(state.playType==="sing"&&state.needSingTick))) {
+  if (state.playState === "play" && getAudioCurrentTime() === 0 && ((state.playType === "play" && state.needTick) || (state.playType === "sing" && state.needSingTick))) {
     // 如果是系统节拍器 等系统节拍器播完了再播,如果是mp3节拍器 直接播
-    if((state.playType==="play" && !state.isOpenMetronome)||(state.playType==="sing" && !state.isSingOpenMetronome)){
+    if ((state.playType === "play" && !state.isOpenMetronome) || (state.playType === "sing" && !state.isSingOpenMetronome)) {
       const tickend = await handleStartTick();
       // console.log("🚀 ~ tickend:", tickend)
       // 节拍器返回false, 取消播放
@@ -740,7 +741,7 @@ export const togglePlay = async (playState?: "play" | "paused", sourceType?: str
         state.playState = "paused";
         return false;
       }
-    }else{
+    } else {
       handleStartTick()
     }
   }
@@ -1228,48 +1229,48 @@ const getMusicInfo = async (res: any) => {
 function xmlToTracks(xmlString: string) {
   const xmlParse = new DOMParser().parseFromString(xmlString, "text/xml");
   const partNames = Array.from(xmlParse.getElementsByTagName('part-name'));
-	return partNames.reduce((arr:string[], item)=>{
+  return partNames.reduce((arr: string[], item) => {
     const textContent = item?.textContent?.trim()
-    if(textContent !== "COMMON" && textContent){
+    if (textContent !== "COMMON" && textContent) {
       arr.push(textContent)
     }
     return arr
-  },[]);
+  }, []);
 }
 // 设置音源
-function initMusicSource (data: any, track?:string) {
+function initMusicSource(data: any, track?: string) {
   const { instrumentId } = storeData.user
   let { musicSheetType, isAllSubject, musicSheetSoundList, musicSheetAccompanimentList } = data
   musicSheetSoundList || (musicSheetSoundList = [])
   musicSheetAccompanimentList || (musicSheetAccompanimentList = [])
-  let musicObj,accompanyObj,fanSongObj,banSongObj
+  let musicObj, accompanyObj, fanSongObj, banSongObj
   // 独奏
-  if(musicSheetType === "SINGLE") {
+  if (musicSheetType === "SINGLE") {
     // 适用声部(instrumentId)为true 时候没有乐器只有一个原音;当前用户有乐器就匹配  不然取第一个原音
-    musicObj = musicSheetSoundList.find((item:any) => {
-      return (isAllSubject||!instrumentId)?item.audioPlayType === "PLAY":(item.audioPlayType === "PLAY"&&item.musicalInstrumentId ==instrumentId)
+    musicObj = musicSheetSoundList.find((item: any) => {
+      return (isAllSubject || !instrumentId) ? item.audioPlayType === "PLAY" : (item.audioPlayType === "PLAY" && item.musicalInstrumentId == instrumentId)
     })
     // 因为可能根据学生的乐器id也找不到曲目所以尝试取第一个
-    musicObj || (musicObj = musicSheetSoundList.find((item:any) => {
+    musicObj || (musicObj = musicSheetSoundList.find((item: any) => {
       return item.audioPlayType === "PLAY"
     }))
-    fanSongObj = musicSheetSoundList.find((item:any) => {
+    fanSongObj = musicSheetSoundList.find((item: any) => {
       return item.audioPlayType === "SING"
     })
-    banSongObj = musicSheetAccompanimentList.find((item:any) => {
+    banSongObj = musicSheetAccompanimentList.find((item: any) => {
       return item.audioPlayType === "SING"
     })
   } else {
     // 合奏  合奏根据声轨来区分原音
-    musicObj = track?musicSheetSoundList.find((item:any) => {
+    musicObj = track ? musicSheetSoundList.find((item: any) => {
       return item.track === track
-    }):musicSheetSoundList[0]
+    }) : musicSheetSoundList[0]
   }
-  accompanyObj = musicSheetAccompanimentList.find((item:any) => {
+  accompanyObj = musicSheetAccompanimentList.find((item: any) => {
     return item.audioPlayType === "PLAY"
   })
   // 当没有任何曲目的时候报错
-  if(!musicObj?.audioFileUrl&&!accompanyObj?.audioFileUrl&&!fanSongObj?.audioFileUrl&&!banSongObj?.audioFileUrl&&!fanSongObj?.solmizationFileUrl){
+  if (!musicObj?.audioFileUrl && !accompanyObj?.audioFileUrl && !fanSongObj?.audioFileUrl && !banSongObj?.audioFileUrl && !fanSongObj?.solmizationFileUrl) {
     throw new Error("该曲目无任何音源");
   }
   Object.assign(state, {
@@ -1278,7 +1279,7 @@ function initMusicSource (data: any, track?:string) {
     fanSong: fanSongObj?.audioFileUrl,
     banSong: banSongObj?.audioFileUrl,
     mingSong: fanSongObj?.solmizationFileUrl
-  })  
+  })
   Object.assign(state.beatSong, {
     music: musicObj?.audioBeatMixUrl,
     accompany: accompanyObj?.audioBeatMixUrl,
@@ -1287,7 +1288,7 @@ function initMusicSource (data: any, track?:string) {
     mingSong: fanSongObj?.solmizationBeatUrl
   })
   return musicObj
-}  
+}
 const setState = (data: any, index: number) => {
   state.appName = "COLEXIU";
   state.detailId = data.bizId;
@@ -1474,10 +1475,10 @@ export const followBeatPaly = () => {
 // 音符添加bbox
 export const addNoteBBox = (list: any[]) => {
   const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || {
-		x: 0,
-		y: 0,
-	};
-	const parentLeft = musicContainer.x || 0;
+    x: 0,
+    y: 0,
+  };
+  const parentLeft = musicContainer.x || 0;
   let voicesBBox: any = null;
   for (let i = 0; i < list.length; i++) {
     const note = list[i];
@@ -1560,7 +1561,7 @@ const fillWordColor = () => {
     if (index === currentNote.repeatIdx) {
       lyric?.classList.add('lyricActive')
     }
-  })  
+  })
 }
 
 /** 跳动svgdom */
@@ -1586,9 +1587,9 @@ export const moveSvgDom = (skipNote?: boolean) => {
 watch(
   () => state.playState,
   () => {
-    if(state.isSingleLine){
+    if (state.isSingleLine) {
       // 当在播放中暂停 执行这个方法
-      if(!state.playEnd && state.playState === "paused") {
+      if (!state.playEnd && state.playState === "paused") {
         moveTranslateXNum(0)
         const scrollLeft = smoothAnimationState.osdmScrollDom!.scrollLeft
         smoothAnimationState.osdmScrollDom!.scrollLeft = scrollLeft + smoothAnimationState.translateXNum
@@ -1600,10 +1601,10 @@ watch(
 
 
 /* 根据当前的小节获取前面有多少需要去除的合并小节 */
-function getNeedReduceMultipleRestNum(currMeasureIndex:number){
+function getNeedReduceMultipleRestNum(currMeasureIndex: number) {
   let needReduceMultipleRestNum = 0;
-  for(let noteIndex = 0; noteIndex < state.times.length; noteIndex++){
-    const note =  state.times[noteIndex];
+  for (let noteIndex = 0; noteIndex < state.times.length; noteIndex++) {
+    const note = state.times[noteIndex];
     if (note.MeasureNumberXML > currMeasureIndex) {
       break;
     }
@@ -1615,58 +1616,58 @@ function getNeedReduceMultipleRestNum(currMeasureIndex:number){
 }
 
 watch(
-	() => state.activeMeasureIndex,
-	() => {
+  () => state.activeMeasureIndex,
+  () => {
     // 需要减去的合并小节数
     const needReduceMultipleRestNum = getNeedReduceMultipleRestNum(state.activeMeasureIndex)
     const matchMeasureNum = state.activeMeasureIndex - needReduceMultipleRestNum - 1
-    console.log('选中的小节',matchMeasureNum,'需要减去的小节',needReduceMultipleRestNum,'当前的小节',state.activeMeasureIndex)
+    console.log('选中的小节', matchMeasureNum, '需要减去的小节', needReduceMultipleRestNum, '当前的小节', state.activeMeasureIndex)
     state.vfmeasures.forEach((item: any, idx: number) => {
       if (idx === matchMeasureNum) {
         item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#132D4C")
         item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#040D1E")
       } else {
         // 有选段只清除选段处的
-        if(state.section.length === 2){
+        if (state.section.length === 2) {
           // 把中间置灰
           let leftMeasureNumberXML = state.section[0].MeasureNumberXML
           let rightMeasureNumberXML = state.section[1].MeasureNumberXML
-          if(leftMeasureNumberXML > rightMeasureNumberXML){
+          if (leftMeasureNumberXML > rightMeasureNumberXML) {
             leftMeasureNumberXML = state.section[1].MeasureNumberXML
             rightMeasureNumberXML = state.section[0].MeasureNumberXML
           }
           const leftVfmeasuresIndex = leftMeasureNumberXML - getNeedReduceMultipleRestNum(leftMeasureNumberXML) - 1
           const rightVfmeasuresIndex = rightMeasureNumberXML - getNeedReduceMultipleRestNum(rightMeasureNumberXML) - 1
-          if(idx >= leftVfmeasuresIndex && idx <= rightVfmeasuresIndex){
+          if (idx >= leftVfmeasuresIndex && idx <= rightVfmeasuresIndex) {
             item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#609FCF")
             item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#2B70A5")
           }
           // 预备小节
-          if(state.sectionFirst){
+          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 {
           item.querySelector('.vf-custom-bg')?.setAttribute("fill", "#609FCF")
           item.querySelector('.vf-custom-bot')?.setAttribute("fill", "#2B70A5")
         }
       }
     })
-	}
-);    
+  }
+);
 
 
 
 watch(
   () => state.section,
   () => {
-    if(state.section.length === 2){
+    if (state.section.length === 2) {
       // 把前面和 后面置灰
       let leftMeasureNumberXML = state.section[0].MeasureNumberXML
       let rightMeasureNumberXML = state.section[1].MeasureNumberXML
-      if(leftMeasureNumberXML > rightMeasureNumberXML){
+      if (leftMeasureNumberXML > rightMeasureNumberXML) {
         leftMeasureNumberXML = state.section[1].MeasureNumberXML
         rightMeasureNumberXML = state.section[0].MeasureNumberXML
       }
@@ -1679,19 +1680,19 @@ watch(
           item.querySelector('.vf-custom-bot')?.setAttribute("fill", "rgba(43,112,165,0.5)")
         }
         // 大于选中置灰
-        if(idx > rightVfmeasuresIndex){
+        if (idx > rightVfmeasuresIndex) {
           item.querySelector('.vf-custom-bg')?.setAttribute("fill", "rgba(96,159,207,0.5)")
           item.querySelector('.vf-custom-bot')?.setAttribute("fill", "rgba(43,112,165,0.5)")
         }
       })
       // 预备小节
-      if(state.sectionFirst){
+      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) => {
@@ -1710,7 +1711,7 @@ watch(
 /** 刷新谱面 */
 export const refreshMusicSvg = () => {
   state.loadingText = '正在加载中,请稍等…'
-  if(state.isSingleLine){
+  if (state.isSingleLine) {
     // 销毁旋律线
     destroySmoothAnimation()
   }
@@ -1721,7 +1722,7 @@ export const refreshMusicSvg = () => {
 watch(
   () => state.setting.displayFingering,
   () => {
-    nextTick(()=>{
+    nextTick(() => {
       if (smoothAnimationState.osdmScrollDom) {
         smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom.offsetWidth | 0
       }

+ 52 - 50
src/style.css

@@ -1,23 +1,25 @@
 * {
-  margin    : 0;
-  padding   : 0;
+  margin: 0;
+  padding: 0;
   box-sizing: border-box;
 }
-img { -webkit-touch-callout: none; }
-body{
+img {
+  -webkit-touch-callout: none;
+}
+body {
   user-select: none;
 }
 
 :root {
-  --cursor-color        : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABRJREFUCB1jZDy49T8DEDCBCBAAACTkAnq23WmtAAAAAElFTkSuQmCC);
+  --cursor-color: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABRJREFUCB1jZDy49T8DEDCBCBAAACTkAnq23WmtAAAAAElFTkSuQmCC);
   --container-background: #fff;
-  --active-stave-box    : rgba(1, 193, 181, 0.2);
-  --corsor-opacity      : 1;
+  --active-stave-box: rgba(1, 193, 181, 0.2);
+  --corsor-opacity: 1;
 }
 
 .eyeProtection {
-  --cursor-color            : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABZJREFUCB1j/N8sv5UBCJhABMN/hv8AKQgEWNQffFMAAAAASUVORK5CYII=);
-  --container-background    : #fff4e1;
+  --cursor-color: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABZJREFUCB1j/N8sv5UBCJhABMN/hv8AKQgEWNQffFMAAAAASUVORK5CYII=);
+  --container-background: #fff4e1;
   --active-measur-backgorund: rgba(255, 98, 37, 0.18);
 }
 
@@ -41,7 +43,7 @@ body{
 
 /* vant 弹窗优化 */
 :root {
-  --van-duration-base: .25s;
+  --van-duration-base: 0.25s;
 }
 
 .van-overlay {
@@ -51,7 +53,7 @@ body{
 .popup-custom {
   transition: all 0.25s;
   background: transparent;
-  overflow  : initial;
+  overflow: initial;
   max-width: 100%;
 }
 
@@ -62,7 +64,7 @@ body{
 /* 缩放动画 */
 .van-scale-enter-from,
 .van-scale-leave-to {
-  opacity  : 0;
+  opacity: 0;
   transform: scale(0.3);
 }
 
@@ -72,13 +74,13 @@ body{
 }
 
 .custom-close-btn .van-popup__close-icon {
-  top             : 0;
-  right           : -30px;
+  top: 0;
+  right: -30px;
   background-color: #fff;
-  border-radius   : 50%;
-  padding         : 4px;
-  font-size       : 16px;
-  color           : var(--van-primary-color);
+  border-radius: 50%;
+  padding: 4px;
+  font-size: 16px;
+  color: var(--van-primary-color);
 }
 
 .top_drag {
@@ -116,7 +118,7 @@ body{
 /* 引导动画 */
 @keyframes guideKeyframes {
   0% {
-    transform: scale(.9);
+    transform: scale(0.9);
   }
 
   50% {
@@ -124,64 +126,64 @@ body{
   }
 
   100% {
-    transform: scale(.9);
+    transform: scale(0.9);
   }
 }
 
 .guideAnimate {
-  animation-duration       : 1.5s;
-  animation-name           : guideKeyframes;
+  animation-duration: 1.5s;
+  animation-name: guideKeyframes;
   animation-iteration-count: infinite;
 }
 
-@keyframes cnimate{
-  0%{
-      opacity: 0;
+@keyframes cnimate {
+  0% {
+    opacity: 0;
   }
-  50%{
-      opacity: 0.5;
+  50% {
+    opacity: 0.5;
   }
-  100%{
-      opacity: 1;
+  100% {
+    opacity: 1;
   }
 }
 
-@keyframes noteAnimate{
-  0%{
-      scale: 0.4;
+@keyframes noteAnimate {
+  0% {
+    scale: 0.4;
   }
-  10%{
+  10% {
     scale: 0.6;
   }
-  20%{
+  20% {
     scale: 0.8;
   }
-  30%{
+  30% {
     scale: 1;
   }
-  40%{
+  40% {
     scale: 1.2;
   }
-  50%{
-      opacity: 1.4;
+  50% {
+    opacity: 1.4;
   }
-  60%{
-      opacity: 1.6;
+  60% {
+    opacity: 1.6;
   }
-  70%{
+  70% {
     opacity: 1.4;
   }
-  80%{
+  80% {
     opacity: 1.2;
-  }  
-  90%{
-      opacity: 1.1;
-  }  
-  100%{
-      opacity: 1;
+  }
+  90% {
+    opacity: 1.1;
+  }
+  100% {
+    opacity: 1;
   }
 }
 
 html {
-  font-size: 64PX;
-}
+  font-size: 64px;
+}

+ 71 - 90
src/view/fingering/index.tsx

@@ -4,98 +4,79 @@ import state from "/src/state";
 import { getFingeringConfig, ITypeFingering } from "./fingering-config";
 
 export default defineComponent({
-	name: "fingering",
-	emits: ["open"],
-	setup(props, { emit }) {
-		const fingerData = reactive({
-			relationshipIndex: 0,
-			subject: null as unknown as ITypeFingering,
-			delay: 0,
-		});
-		const getFingeringData = async () => {
-			fingerData.subject = await getFingeringConfig(state.fingeringInfo.name);
-			console.log("🚀 ~ fingerData.subject:", fingerData.subject)
-		};
-		onBeforeMount(() => {
-			getFingeringData();
-		});
+  name: "fingering",
+  emits: ["open"],
+  setup(props, { emit }) {
+    const fingerData = reactive({
+      relationshipIndex: 0,
+      subject: null as unknown as ITypeFingering,
+      delay: 0,
+    });
+    const getFingeringData = async () => {
+      fingerData.subject = await getFingeringConfig(state.fingeringInfo.name);
+      console.log("🚀 ~ fingerData.subject:", fingerData.subject);
+    };
+    onBeforeMount(() => {
+      getFingeringData();
+    });
 
-		/** 当前音域 */
-		const realKey = computed(() => {
-			return state.times[state.activeNoteIndex]?.realKey || -1;
-		});
+    /** 当前音域 */
+    const realKey = computed(() => {
+      return state.times[state.activeNoteIndex]?.realKey || -1;
+    });
 
-		const doubeClick = () => {
-			const nowTime = Date.now();
-			if (nowTime - fingerData.delay < 300) {
-				emit("open");
-				return;
-			}
-			fingerData.delay = nowTime;
-		};
-		return () => {
-			// console.log("音高", realKey.value);
-			const relationship = fingerData.subject?.relationship?.[realKey.value] || [];
-			const rs: number[] = Array.isArray(relationship[1])
-				? relationship[fingerData.relationshipIndex]
-				: relationship;
-			const canTizhi = Array.isArray(relationship[1]);
-			return (
-				<>
-					{state.fingeringInfo.direction === "transverse" ? (
-						<div onClick={() => doubeClick()} class={[styles.fingeringContainer]}>
+    const doubeClick = () => {
+      const nowTime = Date.now();
+      if (nowTime - fingerData.delay < 300) {
+        emit("open");
+        return;
+      }
+      fingerData.delay = nowTime;
+    };
+    return () => {
+      // console.log("音高", realKey.value);
+      const relationship = fingerData.subject?.relationship?.[realKey.value] || [];
+      const rs: number[] = Array.isArray(relationship[1]) ? relationship[fingerData.relationshipIndex] : relationship;
+      const canTizhi = Array.isArray(relationship[1]);
+      return (
+        <>
+          {state.fingeringInfo.direction === "transverse" ? (
+            <div onClick={() => doubeClick()} class={[styles.fingeringContainer]}>
+              <div class={styles.imgs}>
+                <img class="driver-7" src={fingerData.subject?.json?.full} />
+                {rs.map((key: number | string, index: number) => {
+                  const nk: string = typeof key === "string" ? key.replace("active-", "") : String(key);
+                  return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
+                })}
+              </div>
 
-							<div class={styles.imgs}>
-								<img src={fingerData.subject?.json?.full} />
-								{rs.map((key: number | string, index: number) => {
-									const nk: string = typeof key === "string" ? key.replace("active-", "") : String(key);
-									return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
-								})}
-							</div>
+              {state.fingeringInfo.hasTizhi && (
+                <div class={[styles.tizhi, canTizhi && styles.canDisplay]} onClick={() => (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)}>
+                  替指
+                </div>
+              )}
+            </div>
+          ) : (
+            <div onClick={() => doubeClick()} class={[styles.fingeringContainer, styles.vertical, state.fingeringInfo.name]}>
+              <div class={styles.imgs}>
+                <img class="driver-7" src={fingerData.subject?.json?.full} />
+                {rs.map((key: number | string, index: number) => {
+                  const nk: string = typeof key === "string" ? key.replace("active-", "") : String(key);
+                  return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
+                })}
+              </div>
 
-							{state.fingeringInfo.hasTizhi && (
-								<div
-									class={[styles.tizhi, canTizhi && styles.canDisplay]}
-									onClick={() =>
-										(fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)
-									}
-								>
-									替指
-								</div>
-							)}
-						</div>
-					) : (
-						<div
-							onClick={() => doubeClick()}
-							class={[styles.fingeringContainer, styles.vertical, state.fingeringInfo.name]}
-						>
-							<div class={styles.imgs}>
-								<img src={fingerData.subject?.json?.full} />
-								{rs.map((key: number | string, index: number) => {
-									const nk: string = typeof key === "string" ? key.replace("active-", "") : String(key);
-									return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
-								})}
-							</div>
-
-							{state.fingeringInfo.hasTizhi && (
-								<div
-									style={{ display: state.fingeringInfo.disabledFinger ? "none" : "" }}
-									class={styles.rightContent}
-								>
-									<div
-										class={[styles.tizhi, canTizhi && styles.canDisplay]}
-										onClick={() =>
-											(fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)
-										}
-									>
-										替指
-									</div>
-								</div>
-							)}
-						</div>
-					)}
-				</>
-			);
-		};
-	},
+              {state.fingeringInfo.hasTizhi && (
+                <div style={{ display: state.fingeringInfo.disabledFinger ? "none" : "" }} class={styles.rightContent}>
+                  <div class={[styles.tizhi, canTizhi && styles.canDisplay]} onClick={() => (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)}>
+                    替指
+                  </div>
+                </div>
+              )}
+            </div>
+          )}
+        </>
+      );
+    };
+  },
 });

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 314 - 314
yarn.lock


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác