Ver código fonte

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

TIANYONG 1 ano atrás
pai
commit
4579419700
100 arquivos alterados com 1970 adições e 776 exclusões
  1. 9 8
      dist/colexiu.html
  2. 9 8
      dist/index.html
  3. 12 7
      dist/instrument.html
  4. 10 9
      dist/orchestra.html
  5. 7 6
      dist/report-share.html
  6. 5 0
      instrument.html
  7. 1 1
      osmd-extended
  8. 203 199
      src/constant/instruments.ts
  9. 16 0
      src/helpers/communication.ts
  10. 67 34
      src/helpers/customMusicScore.ts
  11. 15 7
      src/helpers/formateMusic.ts
  12. 8 3
      src/helpers/metronome.ts
  13. 1 1
      src/helpers/midiPlay.tsx
  14. 35 30
      src/hooks/useDrag/dragbom.tsx
  15. 32 21
      src/hooks/useDrag/index.ts
  16. 0 1
      src/hooks/useDrag/useDragGuidance.ts
  17. 3 3
      src/page-instrument/component/authorName/index.module.less
  18. BIN
      src/page-instrument/component/the-music-list/imgs/empty.png
  19. BIN
      src/page-instrument/component/the-music-list/imgs/searImg.png
  20. 116 13
      src/page-instrument/component/the-music-list/index.module.less
  21. 31 3
      src/page-instrument/component/the-music-list/list.tsx
  22. 98 0
      src/page-instrument/custom-plugins/guide-driver/index.less
  23. 411 251
      src/page-instrument/custom-plugins/guide-driver/index.tsx
  24. 32 3
      src/page-instrument/custom-plugins/helper-model/recommendation/index.module.less
  25. 13 4
      src/page-instrument/custom-plugins/helper-model/recommendation/index.tsx
  26. 3 0
      src/page-instrument/custom-plugins/work-ealuating/index.tsx
  27. 2 1
      src/page-instrument/custom-plugins/work-home/index.tsx
  28. 14 1
      src/page-instrument/custom-plugins/work-index/index.tsx
  29. BIN
      src/page-instrument/evaluat-model/countdown/imgs/step1.png
  30. BIN
      src/page-instrument/evaluat-model/countdown/imgs/step2.png
  31. BIN
      src/page-instrument/evaluat-model/countdown/imgs/step3.png
  32. 3 3
      src/page-instrument/evaluat-model/earphone/index.module.less
  33. BIN
      src/page-instrument/evaluat-model/evaluat-result/img/arrow_icon.png
  34. BIN
      src/page-instrument/evaluat-model/evaluat-result/img/close_icon.png
  35. 20 9
      src/page-instrument/evaluat-model/evaluat-result/index.module.less
  36. 11 7
      src/page-instrument/evaluat-model/evaluat-result/index.tsx
  37. BIN
      src/page-instrument/evaluat-model/icons/lanya_erji.png
  38. BIN
      src/page-instrument/evaluat-model/icons/no_erji.png
  39. BIN
      src/page-instrument/evaluat-model/icons/tip_btn.png
  40. BIN
      src/page-instrument/evaluat-model/icons/tip_erji.png
  41. BIN
      src/page-instrument/evaluat-model/icons/youxian_erji.png
  42. 21 0
      src/page-instrument/evaluat-model/index.module.less
  43. 149 27
      src/page-instrument/evaluat-model/index.tsx
  44. 11 0
      src/page-instrument/follow-model/index.module.less
  45. 6 1
      src/page-instrument/follow-model/index.tsx
  46. BIN
      src/page-instrument/follow-model/microphone/images/microBg.png
  47. BIN
      src/page-instrument/follow-model/microphone/images/micro_cancel.png
  48. BIN
      src/page-instrument/follow-model/microphone/images/micro_confirm.png
  49. 28 0
      src/page-instrument/follow-model/microphone/index.module.less
  50. 7 10
      src/page-instrument/follow-model/microphone/index.tsx
  51. BIN
      src/page-instrument/header-top/image/addImg.png
  52. BIN
      src/page-instrument/header-top/image/background.png
  53. BIN
      src/page-instrument/header-top/image/background1.png
  54. BIN
      src/page-instrument/header-top/image/background1Act.png
  55. BIN
      src/page-instrument/header-top/image/backgroundAct.png
  56. BIN
      src/page-instrument/header-top/image/cutImg.png
  57. BIN
      src/page-instrument/header-top/image/gg.png
  58. 0 0
      src/page-instrument/header-top/image/glMode.json
  59. BIN
      src/page-instrument/header-top/image/headImg2.png
  60. BIN
      src/page-instrument/header-top/image/icon_menu.png
  61. BIN
      src/page-instrument/header-top/image/icon_menuAct.png
  62. BIN
      src/page-instrument/header-top/image/icon_pause.png
  63. 0 0
      src/page-instrument/header-top/image/lxMode.json
  64. BIN
      src/page-instrument/header-top/image/mingsong.png
  65. BIN
      src/page-instrument/header-top/image/mingsongAct.png
  66. BIN
      src/page-instrument/header-top/image/music.png
  67. BIN
      src/page-instrument/header-top/image/music1.png
  68. BIN
      src/page-instrument/header-top/image/music1Act.png
  69. BIN
      src/page-instrument/header-top/image/musicAct.png
  70. 0 0
      src/page-instrument/header-top/image/pcMode.json
  71. BIN
      src/page-instrument/header-top/image/perform.png
  72. BIN
      src/page-instrument/header-top/image/performAct.png
  73. BIN
      src/page-instrument/header-top/image/radioActive.png
  74. BIN
      src/page-instrument/header-top/image/section0.png
  75. BIN
      src/page-instrument/header-top/image/section1.png
  76. BIN
      src/page-instrument/header-top/image/section2.png
  77. BIN
      src/page-instrument/header-top/image/shenggui.png
  78. BIN
      src/page-instrument/header-top/image/shengguiAct.png
  79. BIN
      src/page-instrument/header-top/image/sing.png
  80. BIN
      src/page-instrument/header-top/image/singAct.png
  81. BIN
      src/page-instrument/header-top/image/speedBtn.png
  82. BIN
      src/page-instrument/header-top/image/tickoff.png
  83. BIN
      src/page-instrument/header-top/image/tickoffAct.png
  84. BIN
      src/page-instrument/header-top/image/tickon.png
  85. BIN
      src/page-instrument/header-top/image/tickonAct.png
  86. BIN
      src/page-instrument/header-top/image/titBtn.png
  87. 118 24
      src/page-instrument/header-top/index.module.less
  88. 139 47
      src/page-instrument/header-top/index.tsx
  89. 33 2
      src/page-instrument/header-top/modeView.tsx
  90. 46 0
      src/page-instrument/header-top/settting/index.module.less
  91. 50 9
      src/page-instrument/header-top/settting/index.tsx
  92. 53 3
      src/page-instrument/header-top/speed/index.module.less
  93. 73 9
      src/page-instrument/header-top/speed/index.tsx
  94. 11 1
      src/page-instrument/simple-detail/index.module.less
  95. 38 10
      src/page-instrument/simple-detail/index.tsx
  96. BIN
      src/page-instrument/view-detail/images/bg1.png
  97. BIN
      src/page-instrument/view-detail/images/bg2.png
  98. BIN
      src/page-instrument/view-detail/images/bg2_left_zs.png
  99. BIN
      src/page-instrument/view-detail/images/bg2_right_zs.png
  100. BIN
      src/page-instrument/view-detail/images/bg3.png

+ 9 - 8
dist/colexiu.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-846798c8.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-cfb574f0.js"></script>
 
   <meta charset="UTF-8" />
   <link rel="icon" type="image/svg+xml" href="./vite.svg" />
@@ -40,11 +40,12 @@
       },
     })
   </script>
-  <script type="module" crossorigin src="./js/colexiu-29f143fb.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1627fbee.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d7fb97da.js">
-  <link rel="modulepreload" crossorigin href="./js/index-f4e05ab1.js">
-  <link rel="stylesheet" href="./css/index-9cb14b4c.css">
+  <script type="module" crossorigin src="./js/colexiu-234f0b98.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-f30bced8.js">
+  <link rel="modulepreload" crossorigin href="./js/index-563e1186.js">
+  <link rel="modulepreload" crossorigin href="./js/index-faeb03f1.js">
+  <link rel="modulepreload" crossorigin href="./js/index-236e13a9.js">
+  <link rel="stylesheet" href="./css/index-cfe86983.css">
   <link rel="stylesheet" href="./css/colexiu-62f31c4f.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
   <script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy chunks, syntax error above and the same error below should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
@@ -55,8 +56,8 @@
   <img id="loading" class="show" src="./loading.svg" alt="loading" />
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-77fa62bb.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/colexiu-legacy-3bb9bfca.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-241ca397.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/colexiu-legacy-40f75a59.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 9 - 8
dist/index.html

@@ -2,7 +2,7 @@
 <html lang="ZH-cn">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-846798c8.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-cfb574f0.js"></script>
 
   <meta charset="UTF-8">
   <link rel="icon" href="./favicon.ico" />
@@ -75,12 +75,13 @@
       }
     })
   </script>
-  <script type="module" crossorigin src="./js/gym-3eabd42b.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1627fbee.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d7fb97da.js">
-  <link rel="modulepreload" crossorigin href="./js/index-eaf70e9c.js">
+  <script type="module" crossorigin src="./js/gym-94329614.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-f30bced8.js">
+  <link rel="modulepreload" crossorigin href="./js/index-563e1186.js">
+  <link rel="modulepreload" crossorigin href="./js/index-22f26d47.js">
+  <link rel="modulepreload" crossorigin href="./js/index-236e13a9.js">
   <link rel="modulepreload" crossorigin href="./js/plyr.min-c8c2777b.js">
-  <link rel="stylesheet" href="./css/index-9cb14b4c.css">
+  <link rel="stylesheet" href="./css/index-cfe86983.css">
   <link rel="stylesheet" href="./css/index-85f95688.css">
   <link rel="stylesheet" href="./css/plyr-ad8ef5ae.css">
   <link rel="stylesheet" href="./css/index-171cd132.css">
@@ -97,8 +98,8 @@
   <img id="loading" class="show" src="./loading.svg" alt="loading" />
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-77fa62bb.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/gym-legacy-a1a3e413.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-241ca397.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/gym-legacy-097b0596.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 12 - 7
dist/instrument.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-846798c8.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-cfb574f0.js"></script>
 
   <meta charset="UTF-8" />
   <meta name="viewport"
@@ -41,10 +41,10 @@
       })
     }
   </script>
-  <script type="module" crossorigin src="./js/instrument-97316f3e.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1627fbee.js">
-  <link rel="stylesheet" href="./css/index-9cb14b4c.css">
-  <link rel="stylesheet" href="./css/instrument-28f98fa4.css">
+  <script type="module" crossorigin src="./js/instrument-dd2ce7b4.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-f30bced8.js">
+  <link rel="stylesheet" href="./css/index-cfe86983.css">
+  <link rel="stylesheet" href="./css/instrument-9ce1010f.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
   <script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy chunks, syntax error above and the same error below should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
 </head>
@@ -61,9 +61,14 @@
   </script>
 
   
+  <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
+  <script>
+    // VConsole will be exported to `window.VConsole` by default.
+    var vConsole = new window.VConsole();
+  </script>  
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-77fa62bb.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-d4af1dd7.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-241ca397.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-e02d6237.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 10 - 9
dist/orchestra.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-846798c8.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-cfb574f0.js"></script>
 
   <meta charset="UTF-8" />
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
@@ -41,12 +41,13 @@
       transition: opacity .3s;
     }
   </style>
-  <script type="module" crossorigin src="./js/orchestra-eb4b682f.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1627fbee.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d7fb97da.js">
-  <link rel="modulepreload" crossorigin href="./js/index-f4e05ab1.js">
-  <link rel="modulepreload" crossorigin href="./js/index-eaf70e9c.js">
-  <link rel="stylesheet" href="./css/index-9cb14b4c.css">
+  <script type="module" crossorigin src="./js/orchestra-30fef581.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-f30bced8.js">
+  <link rel="modulepreload" crossorigin href="./js/index-563e1186.js">
+  <link rel="modulepreload" crossorigin href="./js/index-faeb03f1.js">
+  <link rel="modulepreload" crossorigin href="./js/index-22f26d47.js">
+  <link rel="modulepreload" crossorigin href="./js/index-236e13a9.js">
+  <link rel="stylesheet" href="./css/index-cfe86983.css">
   <link rel="stylesheet" href="./css/index-85f95688.css">
   <link rel="stylesheet" href="./css/orchestra-8bc1a9c0.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
@@ -69,8 +70,8 @@
   </script>
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-77fa62bb.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/orchestra-legacy-4480abdd.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-241ca397.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/orchestra-legacy-702b578f.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 7 - 6
dist/report-share.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-846798c8.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-cfb574f0.js"></script>
 
   <meta charset="UTF-8" />
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
@@ -25,10 +25,11 @@
       transition: opacity .3s;
     }
   </style>
-  <script type="module" crossorigin src="./js/report-share-e8af6aad.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1627fbee.js">
+  <script type="module" crossorigin src="./js/report-share-2b948aea.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-f30bced8.js">
   <link rel="modulepreload" crossorigin href="./js/plyr.min-c8c2777b.js">
-  <link rel="stylesheet" href="./css/index-9cb14b4c.css">
+  <link rel="modulepreload" crossorigin href="./js/index-236e13a9.js">
+  <link rel="stylesheet" href="./css/index-cfe86983.css">
   <link rel="stylesheet" href="./css/plyr-ad8ef5ae.css">
   <link rel="stylesheet" href="./css/report-share-0f4c3151.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
@@ -51,8 +52,8 @@
   </script>
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-77fa62bb.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/report-share-legacy-5acedb0b.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-241ca397.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/report-share-legacy-d1f9ef4f.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 5 - 0
instrument.html

@@ -53,6 +53,11 @@
   </script>
 
   <script type="module" src="/src/page-instrument/main.ts"></script>
+  <!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
+  <script>
+    // VConsole will be exported to `window.VConsole` by default.
+    var vConsole = new window.VConsole();
+  </script>   -->
 </body>
 
 </html>

+ 1 - 1
osmd-extended

@@ -1 +1 @@
-Subproject commit ce9f135f2993b7b6955eb0376f7e89aa22e7041b
+Subproject commit bab1ca04065946e20f10fa1aacdb0c25b533a6f2

+ 203 - 199
src/constant/instruments.ts

@@ -1,203 +1,207 @@
 const instruments: any = {
-	"Acoustic Grand Piano": "大钢琴",
-	"Bright Acoustic Piano": "明亮的钢琴",
-	"Electric Grand Piano": "电钢琴",
-	"Rhodes Piano": "柔和的电钢琴",
-	"Chorused Piano": "加合唱效果的电钢琴",
-	Harpsichord: "羽管键琴",
-	Clavichord: "科拉维科特琴",
-	Celesta: "钢片琴",
-	Glockenspiel: "钢片琴",
-	"Music box": "八音盒",
-	Vibraphone: "颤音琴",
-	Marimba: "马林巴",
-	Xylophone: "木琴",
-	"Tubular Bells": "管钟",
-	Dulcimer: "大扬琴",
-	"Hammond Organ": "击杆风琴",
-	"Percussive Organ": "打击式风琴",
-	"Rock Organ": "摇滚风琴",
-	"Church Organ": "教堂风琴",
-	"Reed Organ": "簧管风琴",
-	Accordian: "手风琴",
-	Harmonica: "口琴",
-	"Tango Accordian": "探戈手风琴",
-	"Acoustic Guitar": "钢弦吉他",
-	"Electric Guitar": "闷音电吉他",
-	"Overdriven Guitar": "加驱动效果的电吉他",
-	"Distortion Guitar": "加失真效果的电吉他",
-	"Guitar Harmonics": "吉他和音",
-	"Acoustic Bass": "大贝司",
-	"Electric Bass": "电贝司",
-	"Fretless Bass": "无品贝司",
-	"Slap Bass": "掌击",
-	"Synth Bass": "电子合成",
-	Violin: "小提琴",
-	Viola: "中提琴",
-	Cello: "大提琴",
-	Contrabass: "低音大提琴",
-	"Tremolo Strings": "弦乐群颤音音色",
-	"Pizzicato Strings": "弦乐群拨弦音色",
-	"Orchestral Harp": "竖琴",
-	Timpani: "定音鼓",
-	"String Ensemble": "弦乐合奏音色",
-	"Synth Strings": "合成弦乐合奏音色",
-	"Choir Aahs": "人声合唱",
-	"Voice Oohs": "人声",
-	"Synth Voice": "合成人声",
-	"Orchestra Hit": "管弦乐敲击齐奏",
-	Trumpet: "小号",
-	Trombone: "长号",
-	Tuba: "大号",
-	"Muted Trumpet": "加弱音器小号",
-	"French Horn": "法国号",
-	"Brass Section": "铜管组",
-	"Synth Brass": "合成铜管音色",
-	"Soprano Sax": "高音萨克斯管",
-	"Alto Sax": "中音萨克斯管",
-	"Tenor Sax": "次中音萨克斯管",
-	"Baritone Sax": "低音萨克斯管",
-	Oboe: "双簧管",
-	"English Horn": "英国管",
-	Bassoon: "巴松",
-	"Soprano Saxophone": "高音萨克斯管",
-	"Alto Saxophone": "中音萨克斯管",
-	"Tenor Saxophone": "次中音萨克斯管",
-	"Baritone Saxophone": "低音萨克斯管",
-	Piccolo: "短笛",
-	Flute: "长笛",
-	Recorder: "竖笛",
-	"Soprano Recorder": "高音竖笛",
-	"Pan Flute": "排箫",
-	"Bottle Blow": "瓶木管",
-	Whistle: "口哨声",
-	Ocarina: "陶笛",
-	Lead: "合成主音",
-	"Lead lead": "合成主音",
-	"Pad age": "合成音色",
-	Pad: "合成音色",
-	FX: "合成效果  科幻",
-	Sitar: "西塔尔",
-	Banjo: "班卓琴",
-	Shamisen: "三昧线",
-	Koto: "十三弦筝",
-	Kalimba: "卡林巴",
-	Bagpipe: "风笛",
-	Fiddle: "民族提琴",
-	Shanai: "山奈",
-	"Tinkle Bell": "叮当铃",
-	Agogos: "阿戈戈铃",
-	"Steel Drums": "钢鼓",
-	"Taiko Drum": "太鼓",
-	"Melodic Toms": "嗵嗵鼓",
-	"Synth Drums": "合成鼓",
-	"Reverse Cymbals": "反向镲",
-	"Agogo Bells": "阿戈戈铃",
-	"Taiko Drums": "太鼓",
-	Bongos: "邦戈鼓",
-	"Bongo Bell": "邦戈铃",
-	Congas: "康加鼓",
-	Guiro: "刮壶",
-	"Guitar Fret Noise": "吉他换把杂音",
-	"Breath Noise": "呼吸声",
-	Seashore: "海浪声",
-	"Bird Tweet": "鸟鸣",
-	"Telephone Ring": "电话铃",
-	Helicopter: "直升机",
-	Applause: "鼓掌声",
-	Gunshot: "枪声",
-	"Acoustic Bass Drum": "大鼓",
-	"Bass Drum": "大鼓",
-	"Side Drum": "小鼓鼓边",
-	"Acoustic Snare": "小鼓",
-	"Hand Claps": "拍手",
-	"Electric Snare": "小鼓",
-	"Low Floor Tom": "低音嗵鼓",
-	"Closed Hi-Hat": "闭合踩镲",
-	"High Floor Tom": "高音落地嗵鼓",
-	"Pedal Hi-Hat": "脚踏踩镲",
-	"Low Tom": "低音嗵鼓",
-	"Open Hi-Hat": "开音踩镲",
-	"Low-Mid Tom": "中低音嗵鼓",
-	"Hi Mid Tom": "高音鼓",
-	"Crash Cymbals": "对镲",
-	"High Tom": "高音嗵鼓",
-	"Ride Cymbals": "叮叮镲",
-	"Chinese Cymbals": "中国镲",
-	"Ride Bell": "圆铃",
-	Tambourine: "铃鼓",
-	"Splash Cymbal": "溅音镲",
-	Cowbell: "牛铃",
-	"Crash Cymbal": "强音钹",
-	"Vibra-Slap": "颤音器",
-	"Ride Cymbal": "打点钹",
-	"Hi Bongo": "高音邦戈鼓",
-	"Low Bongo": "低音邦戈鼓",
-	"Mute Hi Conga": "弱音高音康加鼓",
-	"Open Hi Conga": "强音高音康加鼓",
-	"Low Conga": "低音康加鼓",
-	"High Timbale": "高音天巴鼓",
-	"Low Timbale": "低音天巴鼓",
-	"High Agogo": "高音阿戈戈铃",
-	"Low Agogo": "低音阿戈戈铃",
-	Cabasa: "卡巴萨",
-	Maracas: "沙锤",
-	"Short Whistle": "短口哨",
-	"Long Whistle": "长口哨",
-	"Short Guiro": "短刮壶",
-	"Long Guiro": "长刮壶",
-	Claves: "响棒",
-	"Hi Wood Block": "高音木鱼",
-	"Low Wood Block": "低音木鱼",
-	"Mute Triangle": "弱音三角铁",
-	"Open Triangle": "强音三角铁",
-	"Drum Set": "架子鼓",
-	"Hulusi flute": "葫芦丝",
-	Melodica: "口风琴",
-	Nai: "口风琴",
-	"Snare Drum": "小军鼓",
-	"Horn in F": "圆号",
-	Triangle: "三角铁",
-	Vibrato: "颤音琴",
-	"Suspend Cymbals": "吊镲",
-	"Suspended Cymbals": "吊镲",
-	"Tom-Toms": "嗵嗵鼓",
-	Bell: "铃铛",
-	Bells: "铃铛",
-	"Alto Clarinet": "中音单簧管",
-	"Bass Clarinet": "低音单簧管",
-	Clarinet: "单簧管",
-	Cornet: "短号",
-	Euphonium: "上低音号",
-	"crash cymbals": "对镲",
-	Castanets: "响板",
-	Shaker: "沙锤",
-	"Mark tree": "音树",
-	Chimes: "管钟",
-	"Mark Tree": "音树",
-	"Tom-toms": "嗵嗵鼓",
-	"Hi-Hat": "踩镲",
-	"Sleigh Bells": "雪橇铃",
-	Flexatone: "弹音器",
-	"Brake drum": "闸鼓",
-	Gong: "锣",
-	"concert tom": "音乐会嗵嗵鼓",
-	"brake drum": "车轮鼓",
-	"finger cymbal": "指钹",
-	"ride cymbal": "叮叮镲",
-	"Concert Toms": "音乐会嗵嗵鼓",
-	Vibraslap: "弹音器",
-	"Wood Blocks": "木鱼",
-	"Temple Blocks": "木鱼",
-	"Wood Block": "木鱼",
-	"Field Drum": "军鼓",
-	"Quad-Toms": "筒鼓",
-	Quads: "筒鼓",
-	"Drums set": "架子鼓",
-	"High Bongo": "邦戈",
-	Timbales: "天巴鼓",
-	Cymbal: "镲",
-	Cymbals: "镲",
+	'Acoustic Grand Piano': '大钢琴',
+	'Bright Acoustic Piano': '明亮的钢琴',
+	'Electric Grand Piano': '电钢琴',
+	'Rhodes Piano': '柔和的电钢琴',
+	'Chorused Piano': '加合唱效果的电钢琴',
+	Harpsichord: '羽管键琴',
+	Clavichord: '科拉维科特琴',
+	Celesta: '钢片琴',
+	Glockenspiel: '钢片琴',
+	'Music box': '八音盒',
+	Vibraphone: '颤音琴',
+	Marimba: '马林巴',
+	Xylophone: '木琴',
+	'Tubular Bells': '管钟',
+	Dulcimer: '大扬琴',
+	'Hammond Organ': '击杆风琴',
+	'Percussive Organ': '打击式风琴',
+	'Rock Organ': '摇滚风琴',
+	'Church Organ': '教堂风琴',
+	'Reed Organ': '簧管风琴',
+	Accordian: '手风琴',
+	Harmonica: '口琴',
+	'Tango Accordian': '探戈手风琴',
+	'Acoustic Guitar': '钢弦吉他',
+	'Electric Guitar': '闷音电吉他',
+	'Overdriven Guitar': '加驱动效果的电吉他',
+	'Distortion Guitar': '加失真效果的电吉他',
+	'Guitar Harmonics': '吉他和音',
+	'Acoustic Bass': '大贝司',
+	'Electric Bass': '电贝司',
+	'Fretless Bass': '无品贝司',
+	'Slap Bass': '掌击',
+	'Synth Bass': '电子合成',
+	Violin: '小提琴',
+	Viola: '中提琴',
+	Cello: '大提琴',
+	Contrabass: '低音大提琴',
+	'Tremolo Strings': '弦乐群颤音音色',
+	'Pizzicato Strings': '弦乐群拨弦音色',
+	'Orchestral Harp': '竖琴',
+	Timpani: '定音鼓',
+	'String Ensemble': '弦乐合奏音色',
+	'Synth Strings': '合成弦乐合奏音色',
+	'Choir Aahs': '人声合唱',
+	'Voice Oohs': '人声',
+	'Synth Voice': '合成人声',
+	'Orchestra Hit': '管弦乐敲击齐奏',
+	Trumpet: '小号',
+	Trombone: '长号',
+	Tuba: '大号',
+	'Muted Trumpet': '加弱音器小号',
+	'French Horn': '法国号',
+	'Brass Section': '铜管组',
+	'Synth Brass': '合成铜管音色',
+	'Soprano Sax': '高音萨克斯管',
+	'Alto Sax': '中音萨克斯管',
+	'Tenor Sax': '次中音萨克斯管',
+	'Baritone Sax': '低音萨克斯管',
+	Oboe: '双簧管',
+	'English Horn': '英国管',
+	Bassoon: '巴松',
+	'Soprano Saxophone': '高音萨克斯管',
+	'Alto Saxophone': '中音萨克斯管',
+	'Tenor Saxophone': '次中音萨克斯管',
+	'Baritone Saxophone': '低音萨克斯管',
+	Piccolo: '短笛',
+	Flute: '长笛',
+	Recorder: '竖笛',
+	'Soprano Recorder': '高音竖笛',
+	'Pan Flute': '排箫',
+	'Bottle Blow': '瓶木管',
+	Whistle: '口哨声',
+	Ocarina: '陶笛',
+	Lead: '合成主音',
+	'Lead lead': '合成主音',
+	'Pad age': '合成音色',
+	Pad: '合成音色',
+	FX: '合成效果  科幻',
+	Sitar: '西塔尔',
+	Banjo: '班卓琴',
+	Shamisen: '三昧线',
+	Koto: '十三弦筝',
+	Kalimba: '卡林巴',
+	Bagpipe: '风笛',
+	Fiddle: '民族提琴',
+	Shanai: '山奈',
+	'Tinkle Bell': '叮当铃',
+	Agogos: '阿戈戈铃',
+	'Steel Drums': '钢鼓',
+	'Taiko Drum': '太鼓',
+	'Melodic Toms': '嗵嗵鼓',
+	'Synth Drums': '合成鼓',
+	'Reverse Cymbals': '反向镲',
+	'Agogo Bells': '阿戈戈铃',
+	'Taiko Drums': '太鼓',
+	Bongos: '邦戈鼓',
+	'Bongo Bell': '邦戈铃',
+	Congas: '康加鼓',
+	Guiro: '刮壶',
+	'Guitar Fret Noise': '吉他换把杂音',
+	'Breath Noise': '呼吸声',
+	Seashore: '海浪声',
+	'Bird Tweet': '鸟鸣',
+	'Telephone Ring': '电话铃',
+	Helicopter: '直升机',
+	Applause: '鼓掌声',
+	Gunshot: '枪声',
+	'Acoustic Bass Drum': '大鼓',
+	'Bass Drum': '大鼓',
+	'Side Drum': '小鼓鼓边',
+	'Acoustic Snare': '小鼓',
+	'Hand Claps': '拍手',
+	'Electric Snare': '小鼓',
+	'Low Floor Tom': '低音嗵鼓',
+	'Closed Hi-Hat': '闭合踩镲',
+	'High Floor Tom': '高音落地嗵鼓',
+	'Pedal Hi-Hat': '脚踏踩镲',
+	'Low Tom': '低音嗵鼓',
+	'Open Hi-Hat': '开音踩镲',
+	'Low-Mid Tom': '中低音嗵鼓',
+	'Hi Mid Tom': '高音鼓',
+	'Crash Cymbals': '对镲',
+	'High Tom': '高音嗵鼓',
+	'Ride Cymbals': '叮叮镲',
+	'Chinese Cymbals': '中国镲',
+	'Ride Bell': '圆铃',
+	Tambourine: '铃鼓',
+	'Splash Cymbal': '溅音镲',
+	Cowbell: '牛铃',
+	'Crash Cymbal': '强音钹',
+	'Vibra-Slap': '颤音器',
+	'Ride Cymbal': '打点钹',
+	'Hi Bongo': '高音邦戈鼓',
+	'Low Bongo': '低音邦戈鼓',
+	'Mute Hi Conga': '弱音高音康加鼓',
+	'Open Hi Conga': '强音高音康加鼓',
+	'Low Conga': '低音康加鼓',
+	'High Timbale': '高音天巴鼓',
+	'Low Timbale': '低音天巴鼓',
+	'High Agogo': '高音阿戈戈铃',
+	'Low Agogo': '低音阿戈戈铃',
+	Cabasa: '卡巴萨',
+	Maracas: '沙锤',
+	'Short Whistle': '短口哨',
+	'Long Whistle': '长口哨',
+	'Short Guiro': '短刮壶',
+	'Long Guiro': '长刮壶',
+	Claves: '响棒',
+	'Hi Wood Block': '高音木鱼',
+	'Low Wood Block': '低音木鱼',
+	'Mute Triangle': '弱音三角铁',
+	'Open Triangle': '强音三角铁',
+	'Drum Set': '架子鼓',
+	'Hulusi flute': '葫芦丝',
+	Melodica: '口风琴',
+	'Snare Drum': '小军鼓',
+	'Horn in F': '圆号',
+	Triangle: '三角铁',
+	Vibrato: '颤音琴',
+	'Suspend Cymbals': '吊镲',
+	'Suspended Cymbals': '吊镲',
+	'Tom-Toms': '嗵嗵鼓',
+	Bell: '铃铛',
+	Bells: '铃铛',
+	'Alto Clarinet': '中音单簧管',
+	'Bass Clarinet': '低音单簧管',
+	Clarinet: '单簧管',
+	Cornet: '短号',
+	Euphonium: '上低音号',
+	'crash cymbals': '对镲',
+	Castanets: '响板',
+	Shaker: '沙锤',
+	'Mark tree': '音树',
+	Chimes: '管钟',
+	'Mark Tree': '音树',
+	'Tom-toms': '嗵嗵鼓',
+	'Hi-Hat': '踩镲',
+	'Sleigh Bells': '雪橇铃',
+	Flexatone: '弹音器',
+	'Brake drum': '闸鼓',
+	Gong: '锣',
+	'concert tom': '音乐会嗵嗵鼓',
+	'brake drum': '车轮鼓',
+	'finger cymbal': '指钹',
+	'ride cymbal': '叮叮镲',
+	'Concert Toms': '音乐会嗵嗵鼓',
+	Vibraslap: '弹音器',
+	'Wood Blocks': '木鱼',
+	'Temple Blocks': '木鱼',
+	'Wood Block': '木鱼',
+	'Field Drum': '军鼓',
+	'Quad-Toms': '筒鼓',
+	Quads: '筒鼓',
+	'Drums set': '架子鼓',
+	'High Bongo': '邦戈',
+	Timbales: '天巴鼓',
+	'rain stick': '雨棒',
+	'String Bass': '弦乐低音',
+	'Floor Tom': '侧嗵鼓',
+	'Brake Drum': '闸鼓',
+	'Tam-tam': '大锣',
+	Cymbal: '镲',
+	Cymbals: '镲'
 };
 /** 获取分轨名称 */
 export const getInstrumentName = (name = '') => {

+ 16 - 0
src/helpers/communication.ts

@@ -266,6 +266,12 @@ export const api_createMusicPlayer = (content: any) => {
 	return promisefiyPostMessage({ api: "createMusicPlayer", content });
 };
 
+/** 初始化曲谱音频 和效音音频 */
+export const api_updateMusicPlayer = (content: any) => {
+	if (!storeData.isApp) return Promise.resolve({} as any);
+	return promisefiyPostMessage({ api: "createMusicPlayer", content });
+};
+
 /** 进入页面设置常量 */
 export const api_keepScreenLongLight = () => {
 	postMessage({
@@ -506,6 +512,16 @@ export const api_finishDelayCheck = (callback: any) => {
 	listenerMessage("finishDelayCheck", callback);
 };
 
+/** 监听延迟检测成功的回调 */
+export const api_remove_finishDelayCheck = (callback: any) => {
+	removeListenerMessage("finishDelayCheck", callback);
+};
+
+/** 取消监听APP取消延迟检测 */
+export const api_remove_cancelDelayCheck = (callback: any) => {
+	removeListenerMessage("cancelDelayCheck", callback);
+};
+
 /** 监听APP播放进度 */
 export const simple_playProgress = (callback: any) => {
 	listenerMessage("api_playProgress", callback);

+ 67 - 34
src/helpers/customMusicScore.ts

@@ -314,6 +314,10 @@ export const resetFormate = () => {
 		for (let i = 0; i < vftexts.length; i++) {
 			const _text = vftexts[i];
 			for (let j = 0; j < vftexts.length; j++) {
+				if (_text.textContent === 'second time only') {
+					// @ts-ignore
+					_text.style.transform = `translateY(15px)`;
+				}
 				if (_text.parentNode === vftexts[j].parentNode) continue;
 				const result = collisionDetection(_text as SVGAElement, vftexts[j] as SVGAElement);
 				if (result.isCollision) {
@@ -534,43 +538,72 @@ export const resetFormate = () => {
 		};
 
 		// 给小节添加背景色
-		staves.forEach((stave: any) => {
-			const list = [
-				Array.from(stave?.querySelectorAll(".vf-StaveSection") || []),
-				Array.from(stave?.querySelectorAll(".vf-Volta") || []),
-				Array.from(stave?.querySelectorAll(".vf-clef") || []),
-				Array.from(stave?.querySelectorAll(".vf-keysignature") || []),
-				Array.from(stave?.querySelectorAll(".vf-Repetition") || []),
-				Array.from(stave?.getElementsByTagName("text") || []),
-			].flat();
-			try {
-				if (list.length) {
-					list.forEach((_el: any) => {
-						stave?.removeChild(_el)
-						_el?.style?.setProperty("display", "none");
-					});
-				}
-			} catch (error) {}
-			const bbox = stave?.getBBox() || {};
-			const rect = `<rect class="vf-custom-bg" x="${bbox.x}" y="${bbox.y}" width="${bbox.width}" height="${bbox.height}" fill="#609FCF" />`
-			const rectBottom = `<rect class="vf-custom-bot" x="${bbox.x}" y="${bbox.y+bbox.height}" width="${bbox.width}" height="12" fill="#2B70A5" />`
-			const customG = `<g>${rect}${rectBottom}</g>`
-			try {
-				if (list.length) {
-					list.forEach((_el: any) => {
-						stave?.appendChild(_el)
-						_el?.style?.removeProperty("display");
-					});
-				}
-			} catch (error) {}
-			stave.innerHTML = customG + stave.innerHTML;
-		});
-		
-		state.vfmeasures = state.vfmeasures.concat(vfmeasures);
+		if (!state.isCreateImg && !state.isPreView) {
+			staves.forEach((stave: any) => {
+				const list = [
+					Array.from(stave?.querySelectorAll(".vf-StaveSection") || []),
+					Array.from(stave?.querySelectorAll(".vf-Volta") || []),
+					Array.from(stave?.querySelectorAll(".vf-clef") || []),
+					Array.from(stave?.querySelectorAll(".vf-keysignature") || []),
+					Array.from(stave?.querySelectorAll(".vf-Repetition") || []),
+					Array.from(stave?.getElementsByTagName("text") || []),
+				].flat();
+				try {
+					if (list.length) {
+						list.forEach((_el: any) => {
+							stave?.removeChild(_el)
+							_el?.style?.setProperty("display", "none");
+						});
+					}
+				} catch (error) {}
+				const bbox = stave?.getBBox() || {};
+				const bgColor = state.isEvaluatReport ? '#132D4C' : '#609FCF';
+				const botColor = state.isEvaluatReport ? '#040D1E' : '#2B70A5';
+				const rect = `<rect class="vf-custom-bg" x="${bbox.x}" y="${bbox.y}" width="${bbox.width}" height="${bbox.height}" fill=${bgColor} />`
+				const rectBottom = `<rect class="vf-custom-bot" x="${bbox.x}" y="${bbox.y+bbox.height}" width="${bbox.width}" height="7.5" fill=${botColor} />`
+				// const filterDom = `<defs>
+				// 						<filter id="shadow">
+				// 						<feDropShadow dx="5" dy="5" stdDeviation="3" flood-color="black" />
+				// 						</filter>
+				// 					</defs>`
+				const customG = `<g>${rect}${rectBottom}</g>`
+				try {
+					if (list.length) {
+						list.forEach((_el: any) => {
+							stave?.appendChild(_el)
+							_el?.style?.removeProperty("display");
+						});
+					}
+				} catch (error) {}
+				stave.innerHTML = customG + stave.innerHTML;
+			});
+			state.vfmeasures = state.vfmeasures.concat(vfmeasures);
+		}
+
+	}
+	if (!state.isCombineRender && state.isSingleLine) {
+		transSinglePage();
 	}
-	
 	// setTimeout(() => this.resetGlobalText());
 };
+
+// 一行谱时,五线谱/简谱的谱面staffLine,居中显示
+const transSinglePage = () => {
+	if (state.isSingleLine && !state.isSimplePage) {
+		const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect();
+		const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect();
+		if (svgPage && staffLine && svgPage.height > 130) {
+			// 需要上移的距离
+			console.log('need',svgPage.height,staffLine.height)
+			const rate = svgPage.height > 400 ? 1.2 : 2;
+			const needTransTop = (svgPage.height - staffLine.height) / rate;
+			// @ts-ignore
+			document.getElementById('osmdSvgPage1').style.transform = `translateY(-${needTransTop}px)`;
+			// document.querySelector('.staffline').style.transform = `translateY(-${needTransTop}px)`;
+		}
+	}
+}
+
 // 技巧文本
 const resetGlobalText = () => {
 	const svg = container.value.querySelector("svg");

+ 15 - 7
src/helpers/formateMusic.ts

@@ -876,6 +876,9 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			if (note.IsGraceNote) {
 				// 如果是装饰音, 取不是装饰音的时值
 				const voice = note.parentStaffEntry.voiceEntries.find((_v: any) => !_v.isGrace);
+				if (!voice) {
+					continue;
+				}
 				note = voice.notes[0];
 			}
 			note.fixedKey = note.ParentVoiceEntry.ParentVoice.Parent.SubInstruments[0].fixedKey || 0;
@@ -968,17 +971,17 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// }
 			
 			activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[0]] || [];
-
+			const currenrtVfVoices = activeVerticalMeasureList[0]?.vfVoices['1'] ? activeVerticalMeasureList[0]?.vfVoices['1'] : activeVerticalMeasureList[0]?.vfVoices['2'] ? activeVerticalMeasureList[0]?.vfVoices['2'] : null;
 			/**
 			 * TODO:多分轨合并的小节,音符可能没有id,此时就去其它分轨找
 			 */
 			const vmLength = note.sourceMeasure?.verticalMeasureList?.length
 			let currentVmIndex = 0;
-			let hasSvgElement = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[staveNoteIndex];
+			let hasSvgElement = currenrtVfVoices?.tickables[staveNoteIndex];
 			while (!hasSvgElement && vmLength > 1 && currentVmIndex <= vmLength - 1) {
 				currentVmIndex += 1;
 				activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[currentVmIndex]] || [];
-				hasSvgElement = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[staveNoteIndex];
+				hasSvgElement = currenrtVfVoices?.tickables[staveNoteIndex];
 			}
 
 			const { realValue } = iterator.currentTimeStamp;
@@ -1127,7 +1130,10 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// console.log('relaEndtime',noteLength, relativeTime)
 			const fixedKey = note.fixedKey || 0;
 			// const svgElement = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables[si];
-			const svgElement = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[staveNoteIndex];
+			//const svgElement = currenrtVfVoices?.tickables[staveNoteIndex];
+			// 过滤掉ghostnote
+			const filterTickables = currenrtVfVoices?.tickables?.filter((tickable: any) => tickable.attrs?.type !== "GhostNote")
+			const svgElement = filterTickables?.[staveNoteIndex];
 
 			// console.log('si',si,i)
 			// console.log(note.sourceMeasure.MeasureNumberXML,note,svgElement, NoteRealValue, measureLength)
@@ -1157,7 +1163,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				if (["2670"].includes(state.cbsExamSongId)) {
 					// fixtime -= _firstMeasureRealValue * formatBeatUnit(beatUnit) * (60 / beatSpeed);
 				} else {
-					if (difftime > 0) {
+					if (difftime > 0 && !state.isEvxml) {
 						fixtime += difftime;
 						state.fixtime = fixtime;
 					}
@@ -1212,6 +1218,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			if (svgElement?.modifiers?.length) {
 				hasGraceNote = svgElement?.modifiers.some((item: any) => item?.attrs?.type === "GraceNoteGroup")
 			}
+			const filterRepeatIdx = allNotes.filter((item: any) => item.noteId === note.NoteToGraphicalNoteObjectId).length
 			const nodeDetail = {
 				isStaccato: note.voiceEntry.isStaccato(),
 				isRestFlag: note.isRestFlag,
@@ -1259,7 +1266,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				totalMultipleRestMeasures, // 当前小节总的合并小节数
 				measureSpeed,  // 小节速度
 				maxNoteNum: note.maxNoteNum, // 当前小节音符最多的分轨的音符数量
-				repeatIdx: iterator.repeatIdx || 0, // 标记是第几遍循环,从0开始
+				// repeatIdx: iterator.repeatIdx || 0, // 标记是第几遍循环,从0开始
+				repeatIdx: filterRepeatIdx,
 				xmlNoteTime: retain(xmlNoteTime), // xml上音符开始时间 唱名用
 				xmlNoteEndTime: retain(xmlNoteTime + noteLength), //xml上音符结束时间 唱名用
 				xmlMp3BeatFixTime,  //xml上节拍器的时间
@@ -1284,7 +1292,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}		
 			nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail);
 			nodeDetail.duration = nodeDetail.endtime - nodeDetail.time;
-			let tickables = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables || [];
+			let tickables = currenrtVfVoices?.tickables || [];
 			if ([121].includes(state.subjectId)) {
 				tickables = note.sourceMeasure.verticalSourceStaffEntryContainers;
 			}

+ 8 - 3
src/helpers/metronome.ts

@@ -286,14 +286,19 @@ class Metronome {
 			const measureListIndex = note?.noteElement?.sourceMeasure?.measureListIndex;
 			if (measureNumberXML > -1) {
 				if (measureNumberXML != xmlNumber) {
+					// 弱起的时候 根据音符结尾时间减去音符开头时间,得到的不是正常小节的时间,然后平均分配节拍之后,当前节拍间隔会非常短 这里弱起拿正常
+					// 妙极客的弱起不受这个影响
+					let startTime = note.measures[0].time
+					if(!state.isEvxml && i === 0){
+						startTime = note.measures[note.measures.length - 1].endtime - note.measures[0].measureLength
+					}
 					const m = {
 						measureNumberXML: measureNumberXML,
 						measureNumberIndex: measureListIndex,
 						numerator: note?.noteElement?.sourceMeasure?.ActiveTimeSignature?.numerator || 0,
-						start: note.measures[0].time,
+						start: startTime,
 						end: note.measures[note.measures.length - 1].endtime,
-						// bug todo  弱起的时候 根据音符结尾时间减去音符开头时间,得到的不是正常小节的时间,然后平均分配节拍之后,当前节拍间隔会非常短
-						time: note.measures[note.measures.length - 1].endtime - note.measures[0].time,
+						time: note.measures[note.measures.length - 1].endtime - startTime,
 						stave_x: note?.noteElement?.sourceMeasure?.verticalMeasureList?.[0]?.stave?.x || 0,
 						end_x: note?.stave?.end_x || 0 || 0,
 						stepList: [] as number[],

+ 1 - 1
src/helpers/midiPlay.tsx

@@ -27,7 +27,7 @@ export const initMidi = (durationNum: number, midiUrl?: string) => {
       denominator: duration.denominator,
       numerator: duration.numerator,
       originalSpeed: state.originSpeed,
-      interval: 50,
+      interval: 16,
       duration: durationNum * 1000,
     }, () => {
       state.midiPlayIniting = false

+ 35 - 30
src/hooks/useDrag/dragbom.tsx

@@ -1,16 +1,16 @@
-import { defineComponent, computed, reactive, onMounted } from 'vue';
-import styles from './index.module.less';
+import { defineComponent, computed, reactive, onMounted } from "vue";
+import styles from "./index.module.less";
 // 底部拖动区域
 export default defineComponent({
-  name: 'dragBom',
+  name: "dragBom",
   emits: ["guideDone"],
-	props: {
-		/** 是否显示引导 */
-		showGuide: {
-			type: Boolean,
-			default: false,
-		},
-	},
+  props: {
+    /** 是否显示引导 */
+    showGuide: {
+      type: Boolean,
+      default: false,
+    },
+  },
   setup(props, { emit }) {
     const data = reactive({
       guidePos: "bottom" as "bottom" | "left" | "right",
@@ -19,19 +19,20 @@ export default defineComponent({
     const initGuidePos = () => {
       const pageHeight = document.documentElement.clientHeight || document.body.clientHeight;
       const pageWidth = document.documentElement.clientWidth || document.body.clientWidth;
-      const guideHeight = document.querySelector('.bom_guide')?.clientHeight || 0;
-      const guideWidth = document.querySelector('.bom_guide')?.clientWidth || 0;
-      const dragBBox = document.querySelector('.bom_drag')?.getBoundingClientRect();
+      const guideHeight = document.querySelector(".bom_guide")?.clientHeight || 0;
+      const guideWidth = document.querySelector(".bom_guide")?.clientWidth || 0;
+      const dragBBox = document.querySelector(".bom_drag")?.getBoundingClientRect();
       const dragTop = dragBBox?.top || 0;
+      const dragHeight = dragBBox?.height || 0;
       const dragLeft = dragBBox?.left || 0;
       // 引导页出现在下边
-      if (pageHeight - dragTop > guideHeight) {
-        data.guidePos = "bottom"
+      if (pageHeight - dragTop - dragHeight > guideHeight) {
+        data.guidePos = "bottom";
       } else {
         // 引导页出现在左边or右边
-        data.guidePos = dragLeft > guideWidth ? "left" : "right"
+        data.guidePos = dragLeft > guideHeight ? "left" : "right";
       }
-    }
+    };
     onMounted(() => {
       setTimeout(() => {
         initGuidePos();
@@ -39,21 +40,25 @@ export default defineComponent({
     });
     return () => (
       <>
-      <div class={[styles.dragbomBox,"dragbomBox"]}>
-        <div class={[styles.dragBom, 'bom_drag']}>
-          <div class={styles.box}></div>
-          <div class={[styles.box, styles.right]}></div>
+        <div class={[styles.dragbomBox, "dragbomBox"]}>
+          <div class={[styles.dragBom, "bom_drag"]}>
+            <div class={styles.box}></div>
+            <div class={[styles.box, styles.right]}></div>
+          </div>
         </div>
-      </div>
-        {
-          props.showGuide && 
-          <div class={[styles.guide, data.guidePos === "left" && styles.guideLeft, data.guidePos === "right" && styles.guideRight, 'bom_guide']} onClick={() => emit("guideDone")}>
+        {props.showGuide && (
+          <div class={[styles.guide, data.guidePos === "left" && styles.guideLeft, data.guidePos === "right" && styles.guideRight, "bom_guide"]} onClick={() => emit("guideDone")}>
             <div class={styles.guideBg}></div>
-            <div class={styles.guideDone} onClick={(e) => {e.stopPropagation();emit("guideDone")}}></div>
-          </div>          
-        }
-
+            <div
+              class={styles.guideDone}
+              onClick={(e) => {
+                e.stopPropagation();
+                emit("guideDone");
+              }}
+            ></div>
+          </div>
+        )}
       </>
     );
-  }
+  },
 });

+ 32 - 21
src/hooks/useDrag/index.ts

@@ -55,14 +55,14 @@ export default function useDrag(
     return pos.value.left === -1 && pos.value.top === -1
       ? {}
       : {
-          position: 'fixed',
-          left: `${pos.value.left}px`,
-          top: `${pos.value.top}px`,
-          transform: 'initial',
-          transformOrigin: 'initial',
-          margin: 'initial',
-          transition: 'initial'
-        };
+        position: 'fixed',
+        left: `${pos.value.left}px`,
+        top: `${pos.value.top}px`,
+        transform: 'initial',
+        transformOrigin: 'initial',
+        margin: 'initial',
+        transition: 'initial'
+      };
   });
   function initPos() {
     const posCache = getCachePos(useIdDargClass);
@@ -75,7 +75,7 @@ export default function useDrag(
     }
   }
   function refreshPos() {
-    if(pos.value.left === -1 && pos.value.top === -1){
+    if (pos.value.left === -1 && pos.value.top === -1) {
       return
     }
     const boxClassDom = document.querySelector(`.${boxClass}`) as HTMLElement;
@@ -105,19 +105,22 @@ export default function useDrag(
 
 // 拖动
 function drag(el: HTMLElement, parentElement: HTMLElement, pos: Ref<posType>) {
-  function mousedown(e: MouseEvent) {
+  function onDown(e: MouseEvent | TouchEvent) {
+    const isTouchEv = isTouchEvent(e);
+    const event = isTouchEv ? e.touches[0] : e;
     const parentElementRect = parentElement.getBoundingClientRect();
-    const downX = e.clientX;
-    const downY = e.clientY;
+    const downX = event.clientX;
+    const downY = event.clientY;
     const clientWidth = document.documentElement.clientWidth;
     const clientHeight = document.documentElement.clientHeight;
     const maxLeft = clientWidth - parentElementRect.width;
     const maxTop = clientHeight - parentElementRect.height;
     const minLeft = 0;
     const minTop = 0;
-    function onMousemove(e: MouseEvent) {
-      let moveX = parentElementRect.left + (e.clientX - downX);
-      let moveY = parentElementRect.top + (e.clientY - downY);
+    function onMove(e: MouseEvent | TouchEvent) {
+      const event = isTouchEvent(e) ? e.touches[0] : e;
+      let moveX = parentElementRect.left + (event.clientX - downX);
+      let moveY = parentElementRect.top + (event.clientY - downY);
       moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
       moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
       pos.value = {
@@ -125,14 +128,22 @@ function drag(el: HTMLElement, parentElement: HTMLElement, pos: Ref<posType>) {
         left: moveX
       };
     }
-    function onMouseup() {
-      document.removeEventListener('mousemove', onMousemove);
-      document.removeEventListener('mouseup', onMouseup);
+    function onUp() {
+      document.removeEventListener(
+        isTouchEv ? 'touchmove' : 'mousemove',
+        onMove
+      );
+      document.removeEventListener(isTouchEv ? 'touchend' : 'mouseup', onUp);
     }
-    document.addEventListener('mousemove', onMousemove);
-    document.addEventListener('mouseup', onMouseup);
+    document.addEventListener(isTouchEv ? 'touchmove' : 'mousemove', onMove);
+    document.addEventListener(isTouchEv ? 'touchend' : 'mouseup', onUp);
   }
-  el.addEventListener('mousedown', mousedown);
+  el.addEventListener('mousedown', onDown);
+  el.addEventListener('touchstart', onDown);
+}
+
+function isTouchEvent(e: MouseEvent | TouchEvent): e is TouchEvent {
+  return window.TouchEvent && e instanceof window.TouchEvent;
 }
 
 // 缓存

+ 0 - 1
src/hooks/useDrag/useDragGuidance.ts

@@ -34,7 +34,6 @@ export default function useDragGuidance() {
    }
    return {
       guidanceShow,
-      guideInfoData,
       setGuidanceShow
    }
 }

+ 3 - 3
src/page-instrument/component/authorName/index.module.less

@@ -1,8 +1,8 @@
 .authorName{
-    height: 2.4rem;
+    height: 1.8rem;
 }
 .title{
-    width: 216px;
+    width: 280px;
     margin: 0 auto;
     :global{
         .van-notice-bar{
@@ -10,7 +10,7 @@
             line-height: 30px;
             padding: 0;
             font-weight: 600;
-            font-size: 18px;
+            font-size: 22px;
             color: #FFFFFF;
             .van-notice-bar__wrap{
                 justify-content: center;

BIN
src/page-instrument/component/the-music-list/imgs/empty.png


BIN
src/page-instrument/component/the-music-list/imgs/searImg.png


+ 116 - 13
src/page-instrument/component/the-music-list/index.module.less

@@ -10,7 +10,7 @@
             height: 100%;
             .van-tabs__wrap{
                 display: flex;
-                justify-content: right;
+                justify-content: flex-end;
                 height: 30px;
                 .van-tabs__nav--line{
                     padding-bottom: 0;
@@ -25,9 +25,11 @@
                             height: 30px;
                             background: url("./imgs/qtqm.png") no-repeat;
                             background-size: 100% 100%;
+                            background-position-y: 2px;
                             &.van-tab--active{
                                 background: url("./imgs/qtqmAct.png") no-repeat;
                                 background-size: 100% 100%;
+                                background-position-y: 2px;
                             }
                         }                   
                         &:nth-child(2){
@@ -37,9 +39,11 @@
                             height: 30px;
                             background: url("./imgs/zjlx.png") no-repeat;
                             background-size: 100% 100%;
+                            background-position-y: 2px;
                             &.van-tab--active{
                                 background: url("./imgs/zjlxAct.png") no-repeat;
                                 background-size: 100% 100%;
+                                background-position-y: 2px;
                             }
                         }
                     }
@@ -85,15 +89,15 @@
         height: 34px;
         background: #F8F9FC;
         border-radius: 18px;
-        padding: 0 4px 0 12px;
+        padding: 0 4px 0 10px;
         border: 1px solid transparent;
         &.isFocus{
             border-color: #1CACF1;
         }
         >img{
             flex-shrink: 0;
-            width: 14px;
-            height: 14px;
+            width: 16px;
+            height: 16px;
         }
         :global{
             .van-field{
@@ -109,6 +113,7 @@
                     font-weight: 400;
                     font-size: 13px;
                     color: #131415;
+                    caret-color: #1cacf1;
                     &::placeholder{
                         color: #AAAAAA;
                     }
@@ -131,12 +136,87 @@
                 opacity: 0.8;
             }
         }
+        .dropdownMenu{
+            border-right: 1px solid #DADCE5;
+            margin-right: 8px;
+            :global{
+                .van-dropdown-menu__bar{
+                    height: 20px;
+                    background: transparent;
+                    box-shadow: initial;
+                    .van-dropdown-menu__item{
+                        padding: 0 8px 0 0;
+                    }
+                    .van-dropdown-menu__title{
+                        --van-gray-4: #0CA2EA;
+                        font-weight: 400;
+                        font-size: 14px;
+                        color: #0CA2EA;
+                        padding: 0 12px 0 0;
+                        &::after{
+                            right: 0;
+                            opacity: initial;
+                        }
+                    }
+                }
+                .van-dropdown-item.van-dropdown-item--down{
+                    left: 36px;
+                    width: 148px;
+                    margin-top: 7px;
+                    .van-dropdown-item__content{
+                        margin-left: 10px;
+                        padding: 0 10px;
+                        width: 128px;
+                        box-shadow: 0px 2px 14px 0px rgba(0,0,0,0.12);
+                        border-radius: 8px;
+                        .van-cell{
+                            margin-top: 6px;
+                            padding: 0;
+                            font-weight: 400;
+                            font-size: 14px;
+                            color: #323233;
+                            line-height: 32px;
+                            text-align: center;
+                            &::after{
+                                border: none;
+                            }
+                            &:last-child{
+                                margin-bottom: 6px;
+                            }
+                            &.van-dropdown-item__option--active{
+                                background: #EEF8FF;
+                                border-radius: 4px;
+                                color: #1CACF1;
+                                font-weight: 500;
+                            }
+                            .van-cell__value{
+                                display: none;
+                            }
+                        }
+                    }
+                }
+            }
+            &.currItem{
+                :global{
+                    .van-dropdown-menu__bar  .van-dropdown-menu__title{
+                        color: #1CACF1;
+                        --van-gray-4:#1CACF1;
+                    }
+                }
+            }
+        }
     }
     :global{
         .van-list{
             margin-top: 10px;
             height: calc(100% - 44px);
             overflow-y: auto;
+            .van-loading__circular{
+                color: rgba(0,0,0,0.3);
+            }
+            .van-loading__text{
+                color: rgba(0,0,0,0.3);
+            }
         }
     }
 }
@@ -191,11 +271,12 @@
             .usedNum{
                 display: flex;
                 align-items: center;
-                padding: 3px 5px;
-                height: 17px;
+                padding: 0 5px;
+                height: 14px;
                 background: #FFF8F7;
                 border-radius: 4px;
-                border: 1px solid #FFC5C5;
+                border: 0.6px solid #FFC5C5;
+                font-size: 10px;
                 >img{
                     width: 8px;
                     height: 11px;
@@ -203,7 +284,6 @@
                 >div{
                     margin-left: 3px;
                     font-weight: 400;
-                    font-size: 12px;
                     color: #FF6A6A
                 }
             }
@@ -213,14 +293,37 @@
                 font-size: 13px;
                 color: rgba(0,0,0,0.5);
             }
+            .playType,.singType{
+                margin-left: 5px;
+                width: 26px;
+                height: 14px;
+                background: #FFFFFF;
+                border-radius: 3px;
+                text-align: center;
+                line-height: 14px;
+                border: 0.6px solid #15B2FD;
+                font-weight: 400;
+                font-size: 10px;
+                color: #15B2FD;
+                &.singType{
+                    color: #CD8613;
+                    border-color: #CD8613;
+                }
+            }
         }
     }
 }
-.noData {
+.empty{
+    margin-top: 10px;
     display: flex;
-    justify-content: center;
+    flex-direction: column;
     align-items: center;
-    height: 100%;
-    font-size: 14px;
-    color: #999999;
+    >img{
+        width: 182px;
+    }
+    >span{
+        font-size: 14px;
+        color: rgba(0,0,0,0.46);
+        margin-top: 10px;
+    }
 }

+ 31 - 3
src/page-instrument/component/the-music-list/list.tsx

@@ -2,11 +2,12 @@ import { defineComponent, onMounted, reactive, watch } from "vue";
 import styles from "./index.module.less";
 import { api_musicSheetPage } from "../../api";
 import state, { togglePlay } from "/src/state";
-import { List, Image, Field } from "vant";
+import { List, Image, Field, DropdownMenu, DropdownItem } from "vant";
 import { postMessage } from "/src/utils/native-message";
 import qs from "query-string";
 import searImg from "./imgs/searImg.png"
 import huoimg from "./imgs/huo.png"
+import emptyImg from "./imgs/empty.png"
 
 export default defineComponent({
   name: "TheMusicList-list",
@@ -24,6 +25,7 @@ export default defineComponent({
       musicSheetCategoriesId: state.bizMusicCategoryId,
       recentFlag: props.recentFlag ? true : null,
       excludeMusicId: props.recentFlag ? null : state.examSongId,
+      audioPlayTypes:""
     });
     const data = reactive({
       isFocus: false,
@@ -32,12 +34,21 @@ export default defineComponent({
       loading: false,
       hasNext: true,
     });
+    const audioPlayTypesOption = [
+      { text: '全部场景', value: "" },
+      { text: '演奏', value: "PLAY" },
+      { text: '演唱', value: "SING" },
+      { text: '演奏+演唱', value: "PLAY,SING" },
+    ]
     const getList = async () => {
       if (!data.hasNext) return;
       data.loading = true;
       try {
         const res = await api_musicSheetPage({
           ...forms,
+          ...{
+            audioPlayTypes: forms.audioPlayTypes ? forms.audioPlayTypes.split(",") : []
+          }
         });
         if (res?.code === 200 && Array.isArray(res.data?.rows)) {
           data.list = [...data.list, ...res.data.rows];
@@ -89,9 +100,17 @@ export default defineComponent({
           _t: Date.now(),
         });
     };
+    function formatNumber(num:number) {
+      return num >= 10000 
+          ? (num / 10000).toFixed(1).replace(/\.0$/, '') + "万" 
+          : num.toString();
+    }
     return () => (
       <div class={styles.wrap}>
         <div class={[styles.searchBox,data.isFocus && styles.isFocus]}>
+          <DropdownMenu class={[styles.dropdownMenu]} overlay={false}>
+							<DropdownItem onChange={handleQuery} v-model={forms.audioPlayTypes} options={audioPlayTypesOption}/>
+					</DropdownMenu>
           <img src={searImg} />
           <Field placeholder="请输入曲目名称" v-model={forms.name} autocomplete="off" onFocus={()=>{ data.isFocus = true }} onBlur={()=>{ data.isFocus = false }} />
           <div class={styles.searchBtn} onClick={handleQuery}>搜索</div>
@@ -115,14 +134,23 @@ export default defineComponent({
                 <div class={styles.content}>
                   <p class={styles.name}>{item.musicSheetName}</p>
                   <div class={styles.detail}>
-                    {item.usedNum && <div class={styles.usedNum}><img src={huoimg}/><div>{item.usedNum}</div></div>}
+                    {item.usedNum && <div class={styles.usedNum}><img src={huoimg}/><div>{formatNumber(item.usedNum)}</div></div>}
+                    {
+                      item.audioPlayTypes && item.audioPlayTypes.split(",").sort().map((type:"PLAY"|"SING")=>{
+                        return <div class={type==="PLAY"?styles.playType:styles.singType}>{type==="PLAY"?"演奏":"演唱"}</div>
+                      })
+                    }
                     {item.composer && <p class={styles.author}>{item.composer}</p>}
                   </div>
                 </div>
               </div>
             );
           })}
-          {!data.loading && data.list.length === 0 && <div class={styles.noData}>暂无数据</div>}
+          {!data.loading && data.list.length === 0 
+            &&  <div class={styles.empty}>
+                  <img src={emptyImg}/>
+                  <span>暂无内容</span>
+                </div>}
         </List>
       </div>
     );

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

@@ -17,6 +17,10 @@
   background-size: contain;
   background-color: transparent !important;
 }
+.popoverClass .driver-popover-prev-btn {
+  font-weight: 600;
+  font-size: 13px;
+}
 
 .popoverClass .driver-popover-next-btn:hover,
 .popoverClass .driver-popover-prev-btn:hover,
@@ -261,6 +265,53 @@
   }
 }
 
+.popoverClassF2 {
+  width: 264px;
+  height: 245px;
+  background: url("./images/follow/f2.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 24px;
+    bottom: 23px;
+  }
+}
+
+.popoverClassF3 {
+  width: 264px;
+  height: 245px;
+  background: url("./images/follow/f3.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 24px;
+    bottom: 23px;
+  }
+
+  &.popoverClose {
+
+    .driver-popover-navigation-btns {
+      position: absolute;
+      bottom: 23px;
+      left: 0;
+      right: 15px;
+      justify-content: flex-start;
+    }
+
+    .driver-popover-next-btn {
+      // right: 14px;
+      // bottom: 18px;
+      position: relative;
+      top: 0;
+      right: 0;
+    }
+
+    .driver-popover-prev-btn {
+      margin-left: 14px;
+    }
+  }
+}
+
 .popoverClassE1 {
   width: 257px;
   height: 247px;
@@ -285,6 +336,53 @@
   }
 }
 
+.popoverClassE3 {
+  width: 264px;
+  height: 245px;
+  background: url("./images/evaluating/e2.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 24px;
+    bottom: 23px;
+  }
+}
+
+.popoverClassE4 {
+  width: 264px;
+  height: 245px;
+  background: url("./images/evaluating/e3.png") no-repeat center;
+  background-size: contain;
+
+  .driver-popover-next-btn {
+    right: 24px;
+    bottom: 23px;
+  }
+
+  &.popoverClose {
+
+    .driver-popover-navigation-btns {
+      position: absolute;
+      bottom: 23px;
+      left: 0;
+      right: 15px;
+      justify-content: flex-start;
+    }
+
+    .driver-popover-next-btn {
+      // right: 14px;
+      // bottom: 18px;
+      position: relative;
+      top: 0;
+      right: 0;
+    }
+
+    .driver-popover-prev-btn {
+      margin-left: 14px;
+    }
+  }
+}
+
 .popoverClassER1 {
   width: 257px;
   height: 178px;

Diferenças do arquivo suprimidas por serem muito extensas
+ 411 - 251
src/page-instrument/custom-plugins/guide-driver/index.tsx


+ 32 - 3
src/page-instrument/custom-plugins/helper-model/recommendation/index.module.less

@@ -14,8 +14,18 @@
     }
     &.evaluating{
         .head{
+            width: 394px;
+            height: 62px;
+            margin-bottom: -3px;
             background: url("/src/page-instrument/header-top/image/headImg2.png") no-repeat;
             background-size: 100% 100%; 
+            .headTit{
+                bottom: 11px;
+            }
+            .closeImg{
+                top: 2px;
+                right: -26px;
+            }
         }
         .content{
             background: #B0CDFF;
@@ -67,6 +77,18 @@
                 width: 0;
                 display: none;
             }
+            .rowCon{
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+                .tit{
+                    margin-right: 2px;
+                    font-family: PingFangSC, PingFang SC;
+                    font-weight: 500;
+                    font-size: 13px;
+                    color: #B6C4D2;
+                }
+            }
             .dropdownMenu{
                 width: 138px;
                 position: relative;
@@ -75,6 +97,7 @@
                         height: 30px;
                         background: #FFFFFF;
                         border-radius: 15px;
+                        box-shadow: initial;
                         .van-dropdown-menu__item{
                             padding: 0 12px;
                         }
@@ -91,10 +114,12 @@
                         }
                     }
                     .recommendationDropdownItem{
-                        top: 112px !important;
-                        left: 24px;
-                        width: 152px;
+                        top: 106px !important;
+                        left: 14px;
+                        width: 172px;
                         .van-dropdown-item__content{
+                            margin: 6px 0 0 10px;
+                            width: 152px;
                             max-height:162px;
                             padding: 0 10px;
                             background: #FFFFFF;
@@ -118,6 +143,7 @@
                                     background: #F2FAFF;
                                     border-radius: 8px;
                                     color: #1CACF1;
+                                    font-weight: 600;
                                 }
                                 .van-cell__value{
                                     display: none;
@@ -160,6 +186,9 @@
                 :global{
                     .van-uploader__preview{
                         margin: 0 8px 0 0;
+                        &:nth-child(5){
+                            margin-right: 0;
+                        }
                         .van-uploader__preview-image{
                             width: 56px;
                             height: 56px;

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

@@ -105,6 +105,12 @@ export default defineComponent({
 		onMounted(() => {
 			getTypeList();
 		});
+		function beforeRead(file:any[]){
+			if(file.length + fileList.value.length > 5){
+				showToast(`最多只能选择5张图片`);
+			}
+			return true
+		}
 		return () => (
 			<div class={[styles.recommendation,styles[state.modeType]]}>
 				<div class={styles.head}>
@@ -113,9 +119,12 @@ export default defineComponent({
 				</div>
 				<div class={styles.content}>
                     <div class={styles.conBox}>
-						<DropdownMenu class={[styles.dropdownMenu, currItem.value && styles.currItem]} overlay={false}>
-							<DropdownItem class={['recommendationDropdownItem']} title={ currItem.value?currItem.value.name:"请选择反馈类型"} v-model={recommenData.suggestId} options={suggestionTypeList.value}/>
-						</DropdownMenu>
+						<div class={styles.rowCon}>
+							<DropdownMenu class={[styles.dropdownMenu, currItem.value && styles.currItem]} overlay={false}>
+								<DropdownItem class={['recommendationDropdownItem']} title={ currItem.value?currItem.value.name:"请选择反馈类型"} v-model={recommenData.suggestId} options={suggestionTypeList.value}/>
+							</DropdownMenu>
+							<div class={styles.tit}>{`${recommenData.message.length >= 200 ? 200 : recommenData.message.length }/200`}</div>
+						</div>
 						<Field
 							class={styles.field}
 							v-model={recommenData.message}
@@ -124,7 +133,7 @@ export default defineComponent({
 							maxlength={200}
 							placeholder="请详细描述您遇到的问题,以便我们尽快为您解决"
 						/>
-						<Uploader accept=".jpg,jpeg,.png" class={styles.uploader} max-size={maxSize * 1024 * 1024} onOversize={onOversize} v-model={fileList.value} after-read={afterRead} multiple max-count={4}>
+						<Uploader accept=".jpg,jpeg,.png" class={styles.uploader} max-size={maxSize * 1024 * 1024} onOversize={onOversize} v-model={fileList.value} beforeRead={beforeRead} after-read={afterRead} multiple max-count={5}>
 							<div class={styles.uploaderbox}>
 								<img class={styles.img} src={headImg("photo.png")}></img>
 								<div>上传图片</div>

+ 3 - 0
src/page-instrument/custom-plugins/work-ealuating/index.tsx

@@ -80,6 +80,9 @@ export default defineComponent({
 			getWorkData();
 			// verifyMembershipServices();
 		});
+		expose({
+			getWorkData
+		})		
 		return () => <div></div>;
 	},
 });

+ 2 - 1
src/page-instrument/custom-plugins/work-home/index.tsx

@@ -102,7 +102,8 @@ export default defineComponent({
 			// verifyMembershipServices();
 		});
 		expose({
-			handleAdd
+			handleAdd,
+			getWorkData
 		})
 		return () => (
 			<div class={styles.homework}>

+ 14 - 1
src/page-instrument/custom-plugins/work-index/index.tsx

@@ -5,8 +5,10 @@ import { getQuery } from "/src/utils/queryString";
 import { api_lessonTrainingTrainingStudentDetail } from "../../api";
 import { headTopData } from "../../header-top";
 import { evaluatingData } from "/src/view/evaluating";
+import state from "/src/state";
 
 const workHomeRef = ref();
+const workEvaluatRef = ref();
 
 export const data = reactive({
 	/** 作业类型:练习PRACTICE, 评测EVALUATION */
@@ -20,6 +22,17 @@ export const HANDLE_WORK_ADD = () => {
 	}
 };
 
+// 刷新谱面后,设置作业选段
+export const resetSection = () => {
+	if (data.trainingType === "PRACTICE"){
+		workHomeRef.value?.getWorkData();
+	}
+	if (data.trainingType === "EVALUATION") {
+		workHomeRef.value?.getWorkData();
+	}
+	state.workSectionNeedReset = false;
+};
+
 export default defineComponent({
 	name: "workIndex",
 	setup(props) {
@@ -56,7 +69,7 @@ export default defineComponent({
 				{data.trainingType === "PRACTICE" && <WorkHome ref={workHomeRef} workeData={data.worke} />}
 				{/* 评测作业 */}
 				{data.trainingType === "EVALUATION" && (
-					<WorkEaluating workeData={data.worke} />
+					<WorkEaluating ref={workEvaluatRef} workeData={data.worke} />
 				)}
 			</>
 		);

BIN
src/page-instrument/evaluat-model/countdown/imgs/step1.png


BIN
src/page-instrument/evaluat-model/countdown/imgs/step2.png


BIN
src/page-instrument/evaluat-model/countdown/imgs/step3.png


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

@@ -1,7 +1,7 @@
 .earphoneBox {
     position: relative;
-    width: 100vw;
-    height: 100vh;
+    width: 600px;
+    height: 229px;
     .earphoneBg {
         position: absolute;
         left: 50%;
@@ -12,7 +12,7 @@
     .earphoneBtn {
         position: absolute;
         left: 50%;
-        bottom: 75px;
+        bottom: 13px;
         width: 133px;
         height: 39px;
         transform: translateX(-43%);

BIN
src/page-instrument/evaluat-model/evaluat-result/img/arrow_icon.png


BIN
src/page-instrument/evaluat-model/evaluat-result/img/close_icon.png


+ 20 - 9
src/page-instrument/evaluat-model/evaluat-result/index.module.less

@@ -131,7 +131,7 @@
                 font-weight: 400;
                 font-size: 12px;
                 color: #8A541E;
-
+                font-family: PingFangSC, PingFang SC;
                 &>span {
                     margin: -2px 4px 0;
                 }
@@ -167,13 +167,20 @@
 
 .ctrls {
     display: flex;
-    justify-content: space-between;
     align-items: center;
+    justify-content: center;
     margin: 0 27px 14px;
 
     .ctrlsBtn {
         width: 107px;
         height: 39px;
+        display: block;
+    }
+    >img:first-child{
+        margin-right: 10px;
+    }
+    >img:last-child{
+        margin-left: 10px;
     }
 }
 
@@ -200,7 +207,7 @@
       min-width: 12Px;
       width: 12Px;
       height: 12Px;
-      background-image: url('./icons/close_icon.png');
+      background-image: url('./img/close_icon.png');
       background-size: 100% 100%;
       background-position: center center;
       background-repeat: no-repeat;
@@ -214,13 +221,14 @@
     .arrowIcon {
       position: absolute;
       left: 50%;
-      bottom: -8PX;
+      bottom: -9PX;
       transform: translateX(-50%);
-      width: 0;
-      height: 0;
-      border-top: 8PX solid rgba(0,0,0,0.7);
-      border-right: 8PX solid transparent;
-      border-left: 8PX solid transparent;  
+      width: 13Px;
+      height: 9Px;
+      background-image: url('./img/arrow_icon.png');
+      background-size: 100% 100%;
+      background-position: center center;
+      background-repeat: no-repeat;
       z-index: 2;      
     }
 }
@@ -299,6 +307,9 @@
         font-size: 18px;
         color: #FF5510;
         line-height: 25px;
+        .scores{
+            font-size: 14px;
+        }
     }
 
 }

+ 11 - 7
src/page-instrument/evaluat-model/evaluat-result/index.tsx

@@ -48,6 +48,8 @@ export default defineComponent({
       console.log("结束", evaluatingData.resultData);
       /** 生成评测记录的时候,记录当前评测的谱面类型,用于评测报告默认展示的谱面类型 */
       evaluatingData.resultData.scoreData.musicType = state.musicRenderType;
+      // 评测的速度,如果是选段,则选选段开头小节的速度
+      const evaluatSpeed = state.sectionStatus && state.section.length === 2 && state.section[0].measureSpeed ? state.section[0].measureSpeed * state.basePlayRate : state.speed;      
       const body = {
         deviceType: browser().android ? "ANDROID" : "IOS", // 设备类型
         intonation: evaluatingData.resultData.intonation, // 音准
@@ -57,7 +59,7 @@ export default defineComponent({
         behaviorId: getBehaviorId(), // 行为id
         sourceTime: getAudioDuration(), // 音频时长
         partIndex: state.partIndex, // 音轨
-        speed: state.speed, // 速度
+        speed: evaluatingData.evaluatSpeed || evaluatSpeed, // 速度
         practiceSource: query.workRecord ? "LESSON_TRAINING" : "EVALUATION", // 练习来源
         score: evaluatingData.resultData.score, // 分数
         clientType: storeData.user.clientType, // 客户端类型
@@ -66,12 +68,14 @@ export default defineComponent({
         playTime: evaluatingData.resultData.playTime / 1000, // 播放时长
         heardLevel: state.setting.evaluationDifficulty, // 听力等级
         recordFilePath: evaluatingData.resultData.url, // 录音文件路径
+        delFlag: evaluatingData.oneselfCancleEvaluating
       };
       data.saveLoading = true;
       const res = await api_musicPracticeRecordSave(body);
       if (res?.code === 200) {
         evaluatingData.resultData.recordId = res.data;
       }
+      evaluatingData.needReplayEvaluat = evaluatingData.oneselfCancleEvaluating ? true : false;
       data.saveLoading = false;
     };
 
@@ -144,31 +148,31 @@ export default defineComponent({
                       <img src={yzImg} />
                       <span>音准</span>
                     </div>
-                    <div>{evaluatingData.resultData.intonation}分</div>
+                    <div>{evaluatingData.resultData.intonation}<span class={styles.scores}></span></div>
                   </div>
                   <div class={styles.progressitem}>
                     <div>
                       <img src={jzImg} />
                       <span>节奏</span>
                     </div>
-                    <div>{evaluatingData.resultData.cadence}分</div>
+                    <div>{evaluatingData.resultData.cadence}<span class={styles.scores}></span></div>
                   </div>
                   <div class={styles.progressitem}>
                     <div>
                       <img src={wzxImg} />
                       <span>完成度</span>
                     </div>
-                    <div>{evaluatingData.resultData.integrity}分</div>
+                    <div>{evaluatingData.resultData.integrity}<span class={styles.scores}></span></div>
                   </div>
                 </div>
               )}
               <div class={styles.tips}>{evaluatingData.resultData.clxtip}</div>
               <div class={styles.ctrls}>
                 <img src={zlycImg} class={[styles.ctrlsBtn, "evaluting-result-2"]} onClick={() => emit("close", "tryagain")} />
-                {!state.isHideEvaluatReportSaveBtn && evaluatingData.resultData.recordId ? (
+                {evaluatingData.resultData.recordId ? (
                   <div class={styles.saveBtn}>
-                    <img src={noSaveTips.value ? bczpJzImg : bczpImg} class={[styles.ctrlsBtn, "evaluting-result-3"]} onClick={() => {
-                      if (!noSaveTips.value) {
+                    <img src={noSaveTips.value ? bczpJzImg : bczpImg} class={[styles.ctrlsBtn, "evaluting-result-3"]} style={{ opacity: state.isHideEvaluatReportSaveBtn ? 0.4 : 1 }} onClick={() => {
+                      if (!noSaveTips.value && !state.isHideEvaluatReportSaveBtn) {
                         saveResult()
                       }
                     }} />

BIN
src/page-instrument/evaluat-model/icons/lanya_erji.png


BIN
src/page-instrument/evaluat-model/icons/no_erji.png


BIN
src/page-instrument/evaluat-model/icons/tip_btn.png


BIN
src/page-instrument/evaluat-model/icons/tip_erji.png


BIN
src/page-instrument/evaluat-model/icons/youxian_erji.png


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

@@ -127,4 +127,25 @@
     width: 100vw;
     height: 100vh;
     z-index: 10000;
+}
+
+.earphoneBox {
+    position: relative;
+    width: 600px;
+    height: 229px;
+    .earphoneBg {
+        position: absolute;
+        left: 50%;
+        top: 0;
+        height: 100%;
+        transform: translateX(-50%);
+    }
+    .earphoneBtn {
+        position: absolute;
+        left: 50%;
+        bottom: 13px;
+        width: 133px;
+        height: 39px;
+        transform: translateX(-43%);
+    }
 }

+ 149 - 27
src/page-instrument/evaluat-model/index.tsx

@@ -1,16 +1,16 @@
-import { Transition, defineComponent, onMounted, reactive, watch, defineAsyncComponent, computed } from "vue";
-import { connectWebsocket, evaluatingData, handleEndBegin, handleStartBegin, handleStartEvaluat, handleViewReport, startCheckDelay, checkUseEarphone, handleCancelEvaluat } from "/src/view/evaluating";
+import { Transition, defineComponent, onMounted, reactive, watch, defineAsyncComponent, computed, onUnmounted } from "vue";
+import { connectWebsocket, evaluatingData, handleEndBegin, handleStartBegin, handleStartEvaluat, handleViewReport, startCheckDelay, checkUseEarphone, handleCancelEvaluat, checkMinInterval, handleEndEvaluat } from "/src/view/evaluating";
 import Earphone from "./earphone";
 import styles from "./index.module.less";
 import SoundEffect from "./sound-effect";
-import state, { handleRessetState, resetPlaybackToStart, musicalInstrumentCodeInfo } from "/src/state";
+import state, { handleRessetState, resetPlaybackToStart, musicalInstrumentCodeInfo, clearSelection, initSetPlayRate, resetBaseRate } from "/src/state";
 import { storeData } from "/src/store";
 import { browser } from "/src/utils";
 import { getNoteByMeasuresSlursStart } from "/src/helpers/formateMusic";
 import { Icon, Popup, showToast, closeToast, showLoadingToast } from "vant";
 import EvaluatResult from "./evaluat-result";
 import EvaluatAudio from "./evaluat-audio";
-import { api_getDeviceDelay, api_openAdjustRecording, api_proxyServiceMessage, api_videoUpdate, getEarphone, api_back, api_startDelayCheck, api_cancelDelayCheck, api_closeDelayCheck, api_finishDelayCheck, api_retryEvaluating } from "/src/helpers/communication";
+import { api_getDeviceDelay, api_openAdjustRecording, api_proxyServiceMessage, api_videoUpdate, getEarphone, api_back, api_startDelayCheck, api_cancelDelayCheck, api_remove_cancelDelayCheck, api_closeDelayCheck, api_finishDelayCheck, api_retryEvaluating, api_remove_finishDelayCheck } from "/src/helpers/communication";
 import EvaluatShare from "./evaluat-share";
 import { Vue3Lottie } from "vue3-lottie";
 import startData from "./data/start.json";
@@ -23,6 +23,8 @@ import { headTopData } from "../header-top/index";
 import { getQuery } from "/src/utils/queryString";
 import Countdown from "./countdown";
 import { IPostMessage } from "/src/utils/native-message";
+import tipErjiBg from "./icons/tip_erji.png"
+import tipErjiBtn from "./icons/tip_btn.png"
 
 // const DelayCheck = defineAsyncComponent(() =>
 //   import('./delay-check')
@@ -38,6 +40,13 @@ type TCriteria = "frequency" | "amplitude" | "decibels";
  */
 let actualBeatLength = 0;
 let calculateInfo: any = {};
+let checkErjiTimer: any = null
+
+export const reCheckDelay = () => {
+  headTopData.settingMode = false
+  state.setting.soundEffect = false
+  api_startDelayCheck({});
+}
 
 export default defineComponent({
   name: "evaluat-model",
@@ -58,8 +67,8 @@ export default defineComponent({
         api_back();
       } else {
         evaluatingData.soundEffectMode = false;
-        handleRessetState();
-        headTopData.modeType = "init";
+        // handleRessetState();
+        // headTopData.modeType = "init";
       }
     };
     /**
@@ -125,6 +134,8 @@ export default defineComponent({
 
     /** 校验耳机状态 */
     const checkEarphoneStatus = async (type?: string) => {
+      clearTimeout(checkErjiTimer);
+      checkErjiTimer = null;
       if (type !== "start") {
         // const erji = await checkUseEarphone();
         const res = await getEarphone();
@@ -133,9 +144,16 @@ export default defineComponent({
         evaluatingData.earphoneMode = true;
         evaluatingData.earPhoneType = res?.content?.type || "";
         if (evaluatingData.earPhoneType === "有线耳机") {
+          clearTimeout(checkErjiTimer);
+          checkErjiTimer = null;
           setTimeout(() => {
             evaluatingData.earphoneMode = false;
           }, 3000);
+        } else {
+          // 如果没有佩戴有限耳机,需要持续检测耳机状态
+          checkErjiTimer = setTimeout(() => {
+            checkEarphoneStatus();
+          }, 1000);
         }
       }
       console.log("检测结束,生成数据", evaluatingData.websocketState, evaluatingData.startBegin, evaluatingData.checkEnd);
@@ -144,6 +162,7 @@ export default defineComponent({
 
     /** 生成评测曲谱数据 */
     const formatTimes = () => {
+      console.log('评测111')
       let starTime = 0;
       let ListenMode = false;
       let dontEvaluatingMode = false;
@@ -177,6 +196,11 @@ export default defineComponent({
       }
       // 阶段评测beatLength需要加上预备小节的持续时长
       actualBeatLength = preTimes.length ? actualBeatLength + preTimes[preTimes.length - 1].relaMeasureLength * 1000 : actualBeatLength;
+      // 如果是弱起,并且预备小节是第一节
+      if (state.section.length && state.sectionFirst && state.sectionFirst.measureListIndex == 0) {
+        actualBeatLength = actualBeatLength < Math.round((state.times[0].fixtime * 1000) / 1) ? Math.round((state.times[0].fixtime * 1000) / 1) : actualBeatLength;
+      }
+      
       let firstNoteTime = unitTestIdx > 1 ? preTime : 0;
       let measureIndex = -1;
       let recordMeasure = -1;
@@ -186,8 +210,9 @@ export default defineComponent({
         const note = getNoteByMeasuresSlursStart(item);
         // #8701 bug: 评测模式,是以曲谱本身的速度进行评测,所以rate取1,不需要转换
         // const rate = state.speed / state.originSpeed;
-        const rate = 1;
-        const difftime = item.difftime;
+        const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
+        // const difftime = item.difftime;
+        const difftime = 0;
         const start = difftime + (item.sourceRelativeTime || item.relativeTime) - starTime;
         const end = difftime + (item.sourceRelaEndtime || item.relaEndtime) - starTime;
         const isStaccato = note.noteElement.voiceEntry.isStaccato();
@@ -225,6 +250,9 @@ export default defineComponent({
           const startId = item.noteElement.tie?.StartNote?.NoteToGraphicalNoteObjectId
           isTenutoSound = item.NoteToGraphicalNoteObjectId === startId ? false : true
         }
+        // 音符是否不需要评测
+        let noteNeedEvaluat = item.hasGraceNote || ListenMode || dontEvaluatingMode || !!item?.voiceEntry?.ornamentContainer || !!item.noteElement?.speedInfo?.startWord?.includes('rit.') || item.skipMode
+        noteNeedEvaluat = noteNeedEvaluat == true ? true : false;
         const data = {
           timeStamp: (start * 1000) / rate,
           duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
@@ -234,12 +262,13 @@ export default defineComponent({
           // 重复的情况index会自然累加,render的index是谱面渲染的index
           measureIndex: measureIndex,
           measureRenderIndex: item.measureListIndex,
-          dontEvaluating: item.hasGraceNote || ListenMode || dontEvaluatingMode || !!item?.voiceEntry?.ornamentContainer || !!item.noteElement?.speedInfo?.startWord?.includes('rit.') || item.skipMode,
+          //  item.MeasureNumberXML >= 1 ? item.MeasureNumberXML - 1 : note.noteElement.sourceMeasure.measureListIndex,
+          dontEvaluating: noteNeedEvaluat,
           musicalNotesIndex: index,
           denominator: note.noteElement?.Length.denominator,
           // isOrnament: !!note?.voiceEntry?.ornamentContainer,
           isTenutoSound,
-          isStaccato: item?.voiceEntry?.isStaccato, // 是否是重音
+          isStaccato: item?.voiceEntry?.isStaccato ? true : false, // 是否是重音
         };
         datas.push(data);
       }
@@ -251,10 +280,14 @@ export default defineComponent({
     /** 连接websocket */
     const handleConnect = async () => {
       const behaviorId = localStorage.getItem("behaviorId") || localStorage.getItem("BEHAVIORID") || undefined;
-      let rate = state.speed / state.originSpeed;
-      rate = parseFloat(rate.toFixed(2));
+      // let rate = state.speed / state.originSpeed;
+      const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
+      // rate = parseFloat(rate.toFixed(2));
       console.log("速度比例", rate, "速度", state.speed);
       calculateInfo = formatTimes();
+      // 评测的速度,如果是选段,则选选段开头小节的速度
+      const evaluatSpeed = state.sectionStatus && state.section.length === 2 && state.section[0].measureSpeed ? state.section[0].measureSpeed * state.basePlayRate : state.speed;
+      evaluatingData.evaluatSpeed = evaluatSpeed;
       const content = {
         musicXmlInfos: calculateInfo.datas,
         subjectId: state.musicalCode,
@@ -267,12 +300,12 @@ export default defineComponent({
         clientId: storeData.platformType === "STUDENT" ? "student" : storeData.platformType === "TEACHER" ? "teacher" : "education",
         hertz: state.setting.frequency,
         reactionTimeMs: state.setting.reactionTimeMs ? Number(state.setting.reactionTimeMs) : 0,
-        speed: state.speed,
+        speed: evaluatSpeed,
         heardLevel: state.setting.evaluationDifficulty,
         // beatLength: Math.round((state.fixtime * 1000) / rate),
         beatLength: actualBeatLength,
         evaluationCriteria: state.evaluationStandard,
-        speedRate: rate, // 播放倍率
+        speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
       };
       await connectWebsocket(content);
       // state.playSource = "music";
@@ -286,15 +319,14 @@ export default defineComponent({
           resetPlaybackToStart();
           return;
         } else if (evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId) {
-          let rate = state.speed / state.originSpeed;
-          rate = parseFloat(rate.toFixed(2));
+          const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
           // 上传云端
           // evaluatModel.evaluatUpdateAudio = true;
           api_openAdjustRecording({
             recordId: evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId,
             title: state.examSongName || "曲谱演奏",
             coverImg: state.coverImg,
-            speedRate: rate, // 播放倍率
+            speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
             musicRenderType: state.musicRenderType,
             musicSheetId: state.examSongId,
             'part-index': state.partIndex
@@ -317,8 +349,11 @@ export default defineComponent({
       } else if (type === "selfCancel") {
         // 再来一次,需要手动取消评测,不生成评测记录,不显示评测结果弹窗
         evaluatingData.oneselfCancleEvaluating = true;
-        handleCancelEvaluat();
-        startBtnHandle();
+        // handleCancelEvaluat();
+        handleEndEvaluat(true, 'selfCancel');
+        // evaluatingData.isBeginMask = true;
+        evaluatingData.evaluatings = {};
+        state.playState = "paused";
       }
       resetPlaybackToStart();
       evaluatingData.resulstMode = false;
@@ -357,6 +392,18 @@ export default defineComponent({
     };
 
     const startBtnHandle = async () => {
+      // 如果打开了延迟检测开关,需要先发送开始检测的消息
+      const delayData = await api_getDeviceDelay();
+      console.log('设备的延迟值',delayData.content?.value)
+      if (delayData && delayData.content?.value <= 0) {
+        await api_startDelayCheck({});
+        return;
+      }
+      evaluatingData.needReplayEvaluat = false;
+      // 选段未完成时,清除选段状态
+      if (state.sectionStatus && state.section.length < 2) {
+        clearSelection();
+      }
       // 如果是异常状态,先等待500ms再执行后续流程
       if (evaluatingData.isErrorState && !state.setting.soundEffect) {
         // console.log('异常流程1')
@@ -376,6 +423,12 @@ export default defineComponent({
         });
       }
       // console.log('异常流程3')
+      // 非选段状态,从头开始评测,重置速度
+      if (!state.sectionStatus && state.section.length === 0) {
+        state.activeNoteIndex = 0;
+        state.speed = state.times[0].measureSpeed * state.basePlayRate
+      }
+      initSetPlayRate();
       // 检测APP端socket状态
       const res: any = await startCheckDelay();
       if (res?.checked) {
@@ -401,9 +454,11 @@ export default defineComponent({
     // 监听APP延迟成功的回调
     const handleFinishDelayCheck = async (res?: IPostMessage) => {
       console.log("监听延迟检测成功", res);
+      evaluatingData.socketErrorPop = false;
       if (res?.content) {
         evaluatingData.checkEnd = true;
-        checkEarphoneStatus();
+        state.setting.soundEffect = false;
+        evaluatingData.tipErjiShow = true;
       }
     };
 
@@ -413,12 +468,41 @@ export default defineComponent({
     };
 
     const earPhonePopShow = computed(() => {
-			return evaluatingData.earphoneMode && state.audioDone && !state.hasDriverPop;
+			return evaluatingData.earphoneMode && !state.isLoading && !state.hasDriverPop;
+		});
+
+    const tipErjiPopShow = computed(() => {
+			return evaluatingData.tipErjiShow && !state.isLoading && !state.hasDriverPop;
 		});
 
+    // watch(
+    //   () => state.setting.soundEffect,
+    //   (val) => {
+    //     if (val) {
+    //       headTopData.settingMode = false
+    //       api_startDelayCheck({});
+    //       state.setting.soundEffect = false
+    //     }
+    //   }
+    // );
+
+    // 手动取消评测,需要自动再次评测
+    // watch(
+    //   () => evaluatingData.needReplayEvaluat,
+    //   (val) => {
+    //     if (val && evaluatingData.oneselfCancleEvaluating) {
+    //       setTimeout(() => {
+    //         startBtnHandle();
+    //       }, 500);
+    //     }
+    //   }
+    // );
+
     onMounted(async () => {
       // 如果打开了延迟检测开关,需要先发送开始检测的消息
-      if (state.setting.soundEffect) {
+      const delayData = await api_getDeviceDelay();
+      console.log('设备的延迟值',delayData.content?.value)
+      if (delayData && delayData.content?.value <= 0) {
         await api_startDelayCheck({});
       } else {
         evaluatingData.checkEnd = true;
@@ -430,10 +514,18 @@ export default defineComponent({
       api_finishDelayCheck(handleFinishDelayCheck);
       api_retryEvaluating(handRetryEvaluating);
     });
+    
+    onUnmounted(() => {
+      api_remove_finishDelayCheck(handleFinishDelayCheck);
+      api_remove_cancelDelayCheck(handleCancelDelayCheck);
+			clearTimeout(checkErjiTimer);
+      checkErjiTimer = null;
+		});
+
     return () => (
       <div>
         <div class={styles.operatingBtn}>
-          {evaluatingData.websocketState && !evaluatingData.startBegin && evaluatingData.checkEnd && (
+          {!evaluatingData.startBegin && (
             <img
               class={[styles.iconBtn, "evaluting-1"]}
               src={headImg("icon_play.png")}
@@ -442,10 +534,26 @@ export default defineComponent({
               }}
             />
           )}
-          {evaluatingData.websocketState && evaluatingData.startBegin && (
+          {evaluatingData.startBegin && (
             <>
-              <img class={styles.iconBtn} src={headImg("icon_reset.png")} onClick={() => handleEvaluatResult("selfCancel")} />
-              <img class={styles.iconBtn} src={headImg("submit.png")} onClick={() => handleEndBegin()} />
+              <img class={styles.iconBtn} src={headImg("icon_reset.png")} onClick={() => {
+                // 校验评测最小间隔时间
+                const currentTime = +new Date();
+                // 开始评测和结束评测的间隔时间小于800毫秒,则不处理
+                if (currentTime - evaluatingData.recordingTime < 800) {
+                  return;
+                }
+                handleEvaluatResult("selfCancel")
+              }} />
+              <img class={styles.iconBtn} src={headImg("submit.png")} onClick={() => {
+                // 校验评测最小间隔时间
+                const currentTime = +new Date();
+                // 开始评测和结束评测的间隔时间小于800毫秒,则不处理
+                if (currentTime - evaluatingData.recordingTime < 800) {
+                  return;
+                }
+                handleEndBegin()
+              }} />
             </>
           )}
         </div>
@@ -466,11 +574,25 @@ export default defineComponent({
         {
           evaluatingData.isBeginMask && <div class={styles.beginMask}></div>
         }
+        <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={tipErjiPopShow.value}>
+          <div class={styles.earphoneBox}>
+            <img class={styles.earphoneBg} src={tipErjiBg} />
+            <img class={styles.earphoneBtn} src={tipErjiBtn} onClick={() => {
+              evaluatingData.tipErjiShow = false;
+              checkEarphoneStatus();
+            }} />
+          </div>
+        </Popup>        
         <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={earPhonePopShow.value}>
           <Earphone
             earphoneType={evaluatingData.earPhoneType}
             onClose={() => {
-              evaluatingData.earphoneMode = false;
+              clearTimeout(checkErjiTimer);
+              checkErjiTimer = null;
+              // #11035,可能刚好关闭耳机弹窗的时候,第二次又出现了弹窗
+              setTimeout(() => {
+                evaluatingData.earphoneMode = false;
+              }, 300);
               // handlePerformDetection();
               checkEarphoneStatus("start");
             }}

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

@@ -84,4 +84,15 @@
           margin-left: 20px;
       }
   }
+  &.operatingLeft {
+    left: 30px !important;
+  }
+}
+.beginMask{
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 10000;
 }

+ 6 - 1
src/page-instrument/follow-model/index.tsx

@@ -37,12 +37,17 @@ export default defineComponent({
 						</div>
 					)}
 				</Transition> */}
-        <div class={styles.operatingBtn}>
+        {/* 遮罩 */}
+        {
+          followData.isBeginMask && <div class={styles.beginMask}></div>
+        }        
+        <div class={[styles.operatingBtn, state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.operatingLeft : ""]}>
           {!followData.start && (
             <img
               class={[styles.iconBtn, "follow-1"]}
               src={headImg("icon_play.png")}
               onClick={() => {
+                followData.start = true;
                 handleFollowStart();
               }}
             />

BIN
src/page-instrument/follow-model/microphone/images/microBg.png


BIN
src/page-instrument/follow-model/microphone/images/micro_cancel.png


BIN
src/page-instrument/follow-model/microphone/images/micro_confirm.png


+ 28 - 0
src/page-instrument/follow-model/microphone/index.module.less

@@ -41,4 +41,32 @@
     width: 100px;
     margin: 18px 6px 17px 6px;
     cursor: pointer;
+}
+
+.microBox {
+    position: relative;
+    width: 100vw;
+    height: 100vh;
+    .microBg {
+        position: absolute;
+        left: 50%;
+        top: 20%;
+        height: 60%;
+        transform: translateX(-50%);
+    }
+    .microCancel {
+        position: absolute;
+        bottom: 85px;
+        left: 39.5%;
+        width: 91px;
+        height: 39px;
+    }
+    .microConfirm {
+        position: absolute;
+        bottom: 85px;
+        right: 28.5%;
+        transform: translateX(-80%);
+        width: 91px;
+        height: 39px;
+    }
 }

+ 7 - 10
src/page-instrument/follow-model/microphone/index.tsx

@@ -3,22 +3,19 @@ import styles from "./index.module.less";
 import icons from "./images/index.json";
 import icon_cancel from "./images/icon_cancel.svg";
 import icon_confirm from "./images/icon_confirm.svg";
+import microBg from "./images/microBg.png";
+import microCancel from "./images/micro_cancel.png";
+import microConfirm from "./images/micro_confirm.png";
 
 export default defineComponent({
 	name: "earphone",
 	emits: ["close"],
 	setup(props, { emit }) {
 		return () => (
-			<div class={styles.fraction}>
-				<img class={styles.erji} src={icons.title} />
-				<div class={styles.content}>
-					<div class={styles.title}>开启权限</div>
-					<div class={styles.tip}>请开启麦克风访问权限</div>
-					<div class={styles.btns}>
-						<img src={icon_cancel} class={styles.btn} onClick={() => emit("close")} />
-						<img src={icon_confirm} class={styles.btn} onClick={() => emit("close")} />
-					</div>
-				</div>
+			<div class={styles.microBox}>
+				<img class={styles.microBg} src={microBg} />
+				<img class={styles.microCancel} src={microCancel} onClick={() => emit("close")} />
+				<img class={styles.microConfirm} src={microConfirm} onClick={() => emit("close")} />
 			</div>
 		);
 	},

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


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


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


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


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


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


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


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
src/page-instrument/header-top/image/glMode.json


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


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


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


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


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
src/page-instrument/header-top/image/lxMode.json


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


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


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


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


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


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


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
src/page-instrument/header-top/image/pcMode.json


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

@@ -34,6 +34,7 @@
         height: 18px;
     }
     &.modeWarnRight {
+        left: inherit;
         right: 30px;
     }
 }
@@ -46,26 +47,25 @@
     .img{
         width: 32px;
         height: 32px;
-        &:first-child{
-            margin-right: 10px;
-        }
+    }
+    .listImg{
+        margin-left: 16px;
     }
     .title{
         width: 216px;
-        &.isMusicList{
-            :global{
-                .van-notice-bar .van-notice-bar__content::after{
-                    position: absolute;
-                    top: 50%;
-                    right: 0;
-                    transform: translateY(-50%);
-                    content: "";
-                    width: 11px;
-                    height: 6px;
-                    background: url("./image/sj.png") no-repeat;
-                    background-size: 100% 100%;                         
-                }
-            }
+        margin-left: 10px;
+        position: relative;
+        .symbolNote{
+            max-width: calc(216px + 16px);
+            position: absolute;
+            top: 0;
+            left: 0;
+            content: "";
+            width: calc(var(--noticeBarWidth,100%) + 16px);
+            height: 100%;
+            background: url("./image/sj.png") no-repeat;
+            background-size: 9px 6px;
+            background-position: center right;                   
         }
         :global{
             .van-notice-bar{
@@ -75,10 +75,6 @@
                 font-weight: 600;
                 font-size: 18px;
                 color: #FFFFFF;
-                .van-notice-bar__content{
-                    position: relative;
-                    padding-right: 16px;
-                }
             }
         }
     }
@@ -133,6 +129,93 @@
             color: #FFFFFF;
             line-height: 17px;
         }
+        &:active{
+            >span{
+                color: #34D6FF
+            };
+        }
+        &.playType:active{
+            >img:nth-child(1){
+                content: url("./image/performAct.png");
+            }            
+            >img:nth-child(2){
+                content: url("./image/singAct.png");
+            }
+        }
+        &.playSource:active{
+            >img:nth-child(1){
+                content: url("./image/musicAct.png");
+            }            
+            >img:nth-child(2){
+                content: url("./image/backgroundAct.png");
+            }            
+        }      
+        &.songSource:active{
+            >img:nth-child(1){
+                content: url("./image/music1Act.png");
+            }            
+            >img:nth-child(2){
+                content: url("./image/background1Act.png");
+            }            
+            >img:nth-child(3){
+                content: url("./image/mingsongAct.png");
+            }
+        }
+        &.section:active{
+            >img{
+                content: url("./image/section2.png");
+            }
+        }
+        &.isSection{
+            >span{
+                color: #34D6FF
+            };
+        }
+        &.speed:active{
+            >img:nth-child(1){
+                content: url("./image/tickonAct.png");
+            }            
+            >img:nth-child(2){
+                content: url("./image/tickoffAct.png");
+            } 
+        }
+        &.isSpeed{
+            >img:nth-child(1){
+                content: url("./image/tickonAct.png");
+            }            
+            >img:nth-child(2){
+                content: url("./image/tickoffAct.png");
+            } 
+            >span{
+                color: #34D6FF
+            }; 
+        }      
+        &.settingMode:active{
+            >img{
+                content: url("./image/icon_menuAct.png");
+            }            
+        }  
+        &.isSettingMode{
+            >img{
+                content: url("./image/icon_menuAct.png");
+            }            
+            >span{
+                color: #34D6FF
+            }; 
+        }  
+        &.musicSheet:active{
+            >img{
+                content: url("./image/shengguiAct.png");
+            }            
+        }       
+        &.isMusicSheet{
+            >img{
+                content: url("./image/shengguiAct.png");
+            }            
+            >span{
+                color: #34D6FF
+            }; 
+        }
     }
     .metronomeBtn{
         position: relative;
@@ -200,8 +283,8 @@
         left: 50%;
         top: 50%;
         transform: translate(-50%, -50%);
-        width: 85%;
-        height: 85%;
+        width: 43px;
+        height: 43px;
     }
 }
 
@@ -224,7 +307,7 @@
     }
 
     &.pauseRightButton {
-        right: 88px !important;
+        right: 108px !important;
         left: auto !important;
         bottom: 12px !important;
     }
@@ -294,4 +377,15 @@
             max-width: 220px;
         }
     }
+}
+
+.hiddenPop {
+    width: 1px;
+    height: 1px;
+    overflow: hidden;
+    opacity: 0;
+}
+
+.socketErrorStatus{
+    top: 20vh;
 }

+ 139 - 47
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, nextTick } from "vue";
 import styles from "./index.module.less";
 
 import iconBack from "./image/icon-back.png";
@@ -31,7 +31,7 @@ import { getGuidance, setGuidance } from "../custom-plugins/guide-page/api";
 import ModeView from "./modeView";
 import { smoothAnimationState } from "../view-detail/smoothAnimation";
 import { isMusicList, musicListShow } from "../component/the-music-list";
-import { EvaluatingDriver, EvaluatingResultDriver, FollowDriver, PractiseDriver } from "../custom-plugins/guide-driver";
+import { EvaluatingDriver, FollowDriver, PractiseDriver } from "../custom-plugins/guide-driver";
 
 /** 头部数据和方法 */
 export const headTopData = reactive({
@@ -94,6 +94,10 @@ export const headTopData = reactive({
     }
     headTopData.modeType = "show";
   },
+  // 改变模式之前的状态
+  oldPlayType: "play",
+  // 记录切换模式前的状态
+  oldModeType: "practise" as "practise" | "follow" | "evaluating"
 });
 
 export const headData = reactive({
@@ -105,6 +109,8 @@ let resetBtn: ComputedRef<{
   display: boolean;
   disabled: boolean;
 }>;
+// 点击切换的时候才触发提醒
+let isClickMode = false
 /**
  * 处理模式切换
  * @param oldPlayType   没改变之前的播放模式
@@ -126,6 +132,15 @@ export function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: I
     // 隐藏重播按钮
     resetBtn && (resetBtn.value.display = false);
   }
+  // 当模式改变的时候  放在这里是因为需要等谱面加载完成之后再提示(点击按钮模式切换才提示)
+  if(isClickMode){
+    showToast({
+      message: state.playType === "play" ? "已切换为演奏场景" : "已切换为演唱场景",
+      position: "top",
+      className: "selectionToast",
+    });
+    isClickMode = false
+  }
 }
 // 模式切换之后重新给times赋值
 function modeChangeHandleTimes(oldPlayType: "play" | "sing", oldPlaySource: IPlayState) {
@@ -248,7 +263,7 @@ export default defineComponent({
     // 是否显示引导
     const showGuide = ref(false);
     const showStudentGuide = ref(false);
-
+    let  displayFingeringCache = false // 指法缓存
     /** 设置按钮 */
     const settingBtn = computed(() => {
       // 音频播放中 禁用
@@ -332,7 +347,7 @@ export default defineComponent({
       // 选择模式 不显示
       if (headTopData.modeType !== "show" || ["follow"].includes(state.modeType)) return { display: false, disabled: true };
       // 音频播放中 禁用
-      if (state.playState === "play") return { display: true, disabled: true };
+      if (state.playState === "play" || query.workRecord) return { display: true, disabled: true };
 
       return {
         disabled: false,
@@ -342,6 +357,8 @@ export default defineComponent({
 
     /** 原声按钮 */
     const originBtn = computed(() => {
+      // 没有音源不显示
+      if(state.noMusicSource) return { display: false, disabled: false };
       // 选择模式,跟练模式 不显示
       if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: false };
       // 评测开始 禁用
@@ -372,12 +389,8 @@ export default defineComponent({
     });
     /** 播放类型按钮 */
     const playTypeBtn = computed(() => {
-      // 选择模式,跟练模式 不显示
-      if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: false };
-      // 评测开始 禁用
-      if (state.modeType === "evaluating") return { display: false, disabled: true };
-      // 音频播放中 禁用
-      if (state.playState === "play") return { display: true, disabled: true };
+      // 选择模式,跟练模式,评测模式 不显示
+      if (headTopData.modeType !== "show" || state.modeType === "follow" || state.modeType === "evaluating") return { display: false, disabled: false };
       if (!state.isAppPlay) {
         let index = 0;
         state.music && index++;
@@ -388,16 +401,24 @@ export default defineComponent({
         state.mingSong && songIndex++;
         // 演唱和演奏 都有数据的时间不禁用
         if (songIndex > 0 && index > 0) {
+          // 音频播放中 禁用
+          if(state.playState === "play"){
+            return { display: true, disabled: true }
+          }
           return { display: true, disabled: false };
         }
       }
       return {
-        disabled: true,
-        display: true,
+        disabled: false,
+        display: false,
       };
     });
     /** 模式切换按钮 */
     const toggleBtn = computed(() => {
+      // 没有音源不显示
+      if(state.noMusicSource) return { display: false, disabled: false };
+      // 不是演奏模式 影藏
+      if(state.playType !== "play") return { display: false, disabled: false }
       // 选择模式, url设置模式 不显示
       if (headTopData.modeType !== "show" || !headTopData.showBack) return { display: false, disabled: false };
       // 跟练开始, 评测开始 播放开始 隐藏
@@ -411,6 +432,8 @@ export default defineComponent({
 
     /** 播放按钮 */
     const playBtn = computed(() => {
+      // 没有音源不显示
+      if(state.noMusicSource) return { display: false, disabled: false };
       // 选择模式 不显示
       if (headTopData.modeType !== "show") return { display: false, disabled: false };
       // 评测模式 不显示,跟练模式 不显示
@@ -425,6 +448,8 @@ export default defineComponent({
 
     /** 重播按钮 */
     resetBtn = computed(() => {
+      // 没有音源不显示
+      if(state.noMusicSource) return { display: false, disabled: false };
       // 选择模式 不显示
       if (headTopData.modeType !== "show") return { display: false, disabled: false };
       // 评测模式 不显示,跟练模式 不显示
@@ -492,7 +517,10 @@ export default defineComponent({
       /** 作业模式 end */
       if (state.defaultModeType == 1) {
         headTopData.handleChangeModeType("practise");
-        if (state.platform === IPlatform.PC || state.isPreView) {
+        // if (state.platform === IPlatform.PC || state.isPreView) {
+        //   headTopData.showBack = false;
+        // }
+        if (state.isPreView) {
           headTopData.showBack = false;
         }
       } else {
@@ -553,7 +581,16 @@ export default defineComponent({
     onUnmounted(() => {
       window.removeEventListener("message", changePlay);
     });
-
+    const noticeBarWidth = ref<number>()
+    watch(()=>smoothAnimationState.isShow.value, ()=>{
+      // NoticeBar能不能滚动
+      if(smoothAnimationState.isShow.value && isMusicList.value){
+        nextTick(()=>{
+          const widthCon = (document.querySelector("#noticeBarRollDom .van-notice-bar__content") as any)?.offsetWidth || undefined
+          noticeBarWidth.value = widthCon
+        })
+      }
+    },{ immediate: true })
     // 设置改变触发
     watch(state.setting, () => {
       console.log(state.setting, "state.setting");
@@ -606,45 +643,55 @@ export default defineComponent({
           {/* 返回和标题 */}
           {
             !(state.playState == "play" || followData.start || evaluatingData.startBegin) &&
-              <div class={styles.headTopLeftBox}>
+              <div id="noticeBarRollDom" class={styles.headTopLeftBox}>
                 <img src={iconBack} class={['headTopBackBtn', styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
                 {
                   smoothAnimationState.isShow.value ?
-                    <div class={[styles.title,isMusicList.value && styles.isMusicList, "driver-8"]} onClick={()=>{
+                    <div 
+                      style={
+                        noticeBarWidth.value ? {
+                          "--noticeBarWidth":noticeBarWidth.value + "px"
+                        } : {}
+                      }
+                      class={[styles.title, "driver-8"]} 
+                      onClick={()=>{
                         isMusicList.value && (musicListShow.value = true)
                       }}>
+                        {
+                          isMusicList.value && <div class={styles.symbolNote}></div>
+                        }
                         <NoticeBar
                           text={state.examSongName}
                           background="none"
                         />
                     </div> :
                     isMusicList.value &&
-                    <img src={listImg} class={[styles.img, "driver-8"]} onClick={()=>{
+                    <img src={listImg} class={[styles.img, styles.listImg, "driver-8"]} onClick={()=>{
                       musicListShow.value = true
                     }} />
                 }
               </div>
           }
           {/* 模式切换 */}
-          { 
-            state.playType === "play" &&
-              <div 
-                id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
-                style={{ display: toggleBtn.value.display ? "" : "none" }}
-                class={["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 
+              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={() => {
+                  headTopData.oldModeType = state.modeType
+                  handleRessetState();
+                  headTopData.modeType = "init";
+              }}
+            >
+              <img class={styles.img} src={iconMode} />
+              <div class={styles.title}>{state.modeType==="practise" ? '练习模式' : state.modeType==="follow" ? "跟练模式" : state.modeType==="evaluating" ? "评测模式" : ""}</div>
+            </div>
           }
           {/* 模式提醒 */}
           {
             state.modeType === "practise" &&
-              <div class={[styles.modeWarn, "practiseModeWarn", state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.modeWarnRight : ""]}>
+            <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>
               </div>
@@ -685,9 +732,10 @@ export default defineComponent({
             ) : null} */}
             <div
               style={{ display: playTypeBtn.value.display ? "" : "none" }}
-              class={["driver-2", styles.btn, playTypeBtn.value.disabled && styles.disabled]}
+              class={["driver-2", styles.btn, playTypeBtn.value.disabled && styles.disabled,styles.playType]}
               onClick={() => {
                 const oldPlayType = state.playType;
+                headTopData.oldPlayType = oldPlayType
                 const oldPlaySource = state.playSource;
                 if (state.playType === "play") {
                   state.playType = "sing";
@@ -696,7 +744,28 @@ export default defineComponent({
                   state.playType = "play";
                   state.playSource = state.music ? "music" : "background";
                 }
-                handlerModeChange(oldPlayType, oldPlaySource, true);
+                isClickMode = true
+                // 有指法并且显示指法的时候 切换到演唱模式 需要影藏指法
+                let isRefresh = false;
+                if (state.isShowFingering && state.fingeringInfo.name && (state.setting.displayFingering || displayFingeringCache)) {
+                  if (state.playType === "sing") {
+                    state.setting.displayFingering = false;
+                    displayFingeringCache = true;
+                  } else {
+                    state.setting.displayFingering = displayFingeringCache;
+                    displayFingeringCache = false;
+                  }
+                  // 如果是竖屏指法和一行谱的时候 改变指法值的时候state 会调用刷新 refreshMusicSvg 所以下面不调用
+                  if (state.fingeringInfo.direction === "vertical" && !state.isSingleLine) {
+                    isRefresh = true;
+                  }
+                }
+                // 有歌词的时候,切换播放模式,需要重新渲染谱面  指法不刷新谱面的时候
+                if (state.xmlHasLyric && !isRefresh) {
+                  refreshMusicSvg();
+                } else if (!isRefresh) {
+                  handlerModeChange(oldPlayType, oldPlaySource, true);
+                }
               }}
             >
               <img style={{ display: state.playType === "play" ? "" : "none" }} class={styles.iconBtn} src={headImg(`perform.png`)} />
@@ -706,7 +775,7 @@ export default defineComponent({
             <div
               id={state.platform === IPlatform.PC ? "teacherTop-1" : "studnetT-1"}
               style={{ display: originBtn.value.display ? "" : "none" }}
-              class={["driver-3", styles.btn, originBtn.value.disabled && styles.disabled]}
+              class={["driver-3", styles.btn, originBtn.value.disabled && styles.disabled,state.playType === "play"?styles.playSource:styles.songSource]}
               onClick={() => {
                 const oldPlayType = state.playType;
                 const oldPlaySource = state.playSource;
@@ -722,6 +791,11 @@ export default defineComponent({
                   }
                 }
                 handlerModeChange(oldPlayType, oldPlaySource);
+                showToast({
+                  message: state.playType === "play" ? (state.playSource === "music" ? "已切换为原声" : "已切换为伴奏") : state.playSource === "music" ? "已切换为范唱" : state.playSource === "background" ? "已切换为伴唱" : "已切换为唱名",
+                  position: "top",
+                  className: "selectionToast",
+                });
               }}
             >
               <img style={{ display: state.playSource === "music" ? "" : "none" }} class={styles.iconBtn} src={state.playType === "play" ? headImg(`music.png`) : headImg(`music1.png`)} />
@@ -729,7 +803,7 @@ export default defineComponent({
               <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>
             </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]} 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, styles.section, state.sectionStatus && styles.isSection]} 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`)} />
@@ -739,7 +813,7 @@ export default defineComponent({
               <>
                 <div
                   style={{ display: metronomeBtn.value.display ? "" : "none" }}
-                  class={["driver-5", styles.btn, styles.metronomeBtn, metronomeBtn.value.disabled && styles.disabled]}
+                  class={["driver-5", styles.btn, styles.metronomeBtn, metronomeBtn.value.disabled && styles.disabled,headData.speedShow && styles.isSpeed,styles.speed]}
                   onClick={async () => {
                     headData.speedShow = !headData.speedShow;
                   }}
@@ -749,7 +823,7 @@ export default defineComponent({
                   <span style={{ whiteSpace: "nowrap" }}>节拍</span>
                   <div class={styles.speedCon}>
                     <img src={headImg("speed.png")} />
-                    <div>{state.speed}</div>
+                    <div>{Math.floor(state.speed)}</div>
                   </div>
                 </div>
                 {
@@ -783,7 +857,7 @@ export default defineComponent({
             ) : null} */}
             {state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert && (
               <div
-                class={[styles.btn, state.playState === "play" && fingeringBtn.value.disabled && styles.disabled]}
+                class={[styles.btn, state.playState === "play" && fingeringBtn.value.disabled && styles.disabled,toggleMusicSheet.show&&styles.isMusicSheet,styles.musicSheet]}
                 onClick={() => {
                   toggleMusicSheet.toggle(true);
                 }}
@@ -792,7 +866,7 @@ export default defineComponent({
                 <span>声部</span>
               </div>
             )}
-            <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)}>
+            <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,headTopData.settingMode&&styles.isSettingMode,styles.settingMode]} onClick={() => (headTopData.settingMode = true)}>
               <img class={styles.iconBtn} src={headImg("icon_menu.png")} />
               <span>设置</span>
             </div>
@@ -810,12 +884,12 @@ export default defineComponent({
             playBtn.value.disabled && styles.disabled,
             state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.playLeftButton : state.platform === IPlatform.PC && state.musicScoreBtnDirection === "right" ? styles.playRightButton : "",
           ]}
-          onClick={() => togglePlay()}
+          onClick={() => togglePlay(state.playState === "play" ? "paused" : "play")}
         >
           <div class={styles.btnWrap}>
             <img style={{ display: state.playState === "play" ? "none" : "" }} class={styles.iconBtn} src={headImg("icon_play.png")} />
             <img style={{ display: state.playState === "play" ? "" : "none" }} class={styles.iconBtn} src={headImg("icon_pause.png")} />
-            <Circle style={{ opacity: state.playState === "play" ? 1 : 0 }} class={styles.progress} stroke-width={80} currentRate={state.playProgress} rate={100} color="#FFED78" layer-color="rgba(0,0,0,0)" />
+            <Circle style={{ opacity: state.playState === "play" ? 1 : 0 }} class={styles.progress} stroke-width={60} stroke-linecap={"square"} currentRate={state.playProgress} rate={100} color="#FFED78" layer-color="rgba(0,0,0,0)" />
           </div>
         </div>
 
@@ -843,13 +917,31 @@ export default defineComponent({
         {isAllBtnsStudent.value && !query.isCbs && showGuideIndex.value && <StudentTop></StudentTop>} */}
 
         {/* 练习模式功能引导 加载音频完成 不是会员 */}
-        {state.modeType === "practise" && !query.isCbs && state.audioDone && !state.isVip && <PractiseDriver />}
+        {state.modeType === "practise" && headTopData.modeType !== "init" && !query.isCbs && state.audioDone && !state.isLoading && !state.isVip && (
+          <PractiseDriver
+            statusAll={{
+              subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
+              modelTypeStatus: toggleBtn.value.display,
+              playType: playTypeBtn.value.display,
+            }}
+          />
+        )}
         {/* 跟练模式功能引导 加载音频完成 不是会员 */}
-        {state.modeType === "follow" && !query.isCbs && state.audioDone && !state.isVip && <FollowDriver />}
+        {state.modeType === "follow" && headTopData.modeType !== "init" && !query.isCbs && state.audioDone && !state.isLoading && !state.isVip && (
+          <FollowDriver
+            statusAll={{
+              subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
+            }}
+          />
+        )}
         {/* 评测模式功能引导 加载音频完成 不是会员 */}
-        {state.modeType === "evaluating" && !evaluatingData.earphoneMode && !query.isCbs && state.audioDone && !state.isVip && <EvaluatingDriver />}
-        {/* 评测模式-结果弹窗 功能引导 加载音频完成 不是会员 */}
-        {state.modeType === "evaluating" && evaluatingData.resulstMode && !evaluatingData.earphoneMode && !query.isCbs && state.audioDone && !state.isVip && <EvaluatingResultDriver />}
+        {state.modeType === "evaluating" && headTopData.modeType !== "init" && !evaluatingData.earphoneMode && !query.isCbs && state.audioDone && !state.isLoading && !state.isVip && evaluatingData.websocketState && !evaluatingData.startBegin && evaluatingData.checkEnd && (
+          <EvaluatingDriver
+            statusAll={{
+              subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
+            }}
+          />
+        )}
       </>
     );
   },

+ 33 - 2
src/page-instrument/header-top/modeView.tsx

@@ -13,6 +13,10 @@ import state from "/src/state";
 import { studentQueryUserInfo } from "../api";
 import { usePageVisibility } from "@vant/use";
 import { Vue3Lottie } from "vue3-lottie";
+import { popImgs, hanldeConfirmPop, hanldeClosePop, evaluatingData } from "/src/view/evaluating"
+import { Popup } from "vant";
+import AbnormalPop from "/src/view/abnormal-pop";
+import { smoothAnimationState } from "../view-detail/smoothAnimation";
 
 export default defineComponent({
   name: "modeView",
@@ -87,23 +91,50 @@ export default defineComponent({
     onMounted(() => {
       openGuid();
     });
+    watch(
+      () => evaluatingData.socketErrorStatus,
+      () => {
+        if (evaluatingData.socketErrorStatus === 2) {
+          setTimeout(() => {
+            evaluatingData.socketErrorPop = false;
+          }, 1000);
+        }
+      }
+    );      
     return () => (
       <div class={[styles.modeView, headTopData.modeType !== "init" && styles.hidden]}>
         <img
           src={backImg}
           class={styles.back}
           onClick={() => {
+            smoothAnimationState.isShow.value = state.melodyLine;
+            // 返回的时候 跳转到之前记录的模式
+            if(headTopData.oldModeType !== "practise"){
+              headTopData.handleChangeModeType(headTopData.oldModeType)
+            }
             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]}>
-          <Vue3Lottie ref={modeImgDom1} class={styles.modeImg} animationData={lxMode} autoPlay={false} loop={true} onClick={() => headTopData.handleChangeModeType("practise")}></Vue3Lottie>
+          <Vue3Lottie ref={modeImgDom1} class={styles.modeImg} animationData={lxMode} autoPlay={false} loop={true} onClick={() => {
+            smoothAnimationState.isShow.value = state.melodyLine;
+            headTopData.handleChangeModeType("practise")
+            } }></Vue3Lottie>
           {!state.isPercussion && <Vue3Lottie ref={modeImgDom2} class={styles.modeImg} animationData={glMode} autoPlay={false} loop={true} onClick={() => headTopData.handleChangeModeType("follow")}></Vue3Lottie>}
           {state.enableEvaluation && <Vue3Lottie ref={modeImgDom3} class={styles.modeImg} animationData={pcMode} autoPlay={false} loop={true} onClick={() => headTopData.handleChangeModeType("evaluating")}></Vue3Lottie>}
         </div>
         {data.showVip && <TheVip />}
+        {/** 延迟检测中途,socket出错,网络提示弹窗 */}
+        {/* {
+          state.modeType !== 'evaluating' && 
+          <div>
+              <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale", evaluatingData.socketErrorStatus === 2 && styles.socketErrorStatus]} transition="van-scale" v-model:show={evaluatingData.socketErrorPop} overlay-style={evaluatingData.socketErrorStatus === 2?{ background: "initial" }:{}}>
+                <AbnormalPop onConfirm={hanldeConfirmPop} onClose={hanldeClosePop} />
+              </Popup>
+          </div>            
+        } */}
       </div>
     );
   },
-});
+});

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

@@ -9,13 +9,26 @@
             box-shadow: 0px 4px 0px 0px #5EA2B9;
             .conBox{
                 background: #E3F3F5;
+                .cellBox{
+                    border-color: #CFE6EC;
+                }
             }
         }
     }
     &.evaluating{
         .head{
+            width: 394px;
+            height: 62px;
+            margin-bottom: -3px;
             background: url("../image/headImg2.png") no-repeat;
             background-size: 100% 100%; 
+            .headTit{
+                bottom: 11px;
+            }
+            .closeImg{
+                top: 2px;
+                right: -26px;
+            }
         }
         .content{
             background: #B0CDFF;
@@ -99,6 +112,18 @@
                     color: #000000;
                     line-height: 21px;
                 }
+                .titbtn {
+                    width: 76px;
+                    height: 26px;
+                    font-weight: 600;
+                    font-size: 14px;
+                    color: #fff;
+                    text-align: center;
+                    line-height: 26px;
+                    background: url("../image/titBtn.png") no-repeat;
+                    background-size: 100% 100%;
+                    cursor: pointer;
+                }
                 .spendCon{
                     flex-grow: 1;
                     display: flex;
@@ -116,6 +141,27 @@
                                     background: linear-gradient( 270deg, #7ADEFF 0%, #29A9FF 100%);
                                     box-shadow: inset 1px 0px 5px 0px rgba(150,254,255,0.79);
                                     border: 1px solid #4A91D4;
+                                    &::after{
+                                        position: absolute;
+                                        content: "";
+                                        left: 4px;
+                                        top: 2px;
+                                        width: 100%;
+                                        height: 1px;
+                                        background: #FFFFFF;
+                                        border-radius: 1px;
+                                        filter: blur(1px);
+                                    }
+                                    &::before{
+                                        position: absolute;
+                                        content: "";
+                                        left: 2px;
+                                        top: 1px;
+                                        width: 4px;
+                                        height: 4px;
+                                        background: url("../image/gg.png") no-repeat;
+                                        background-size: 100% 100%;
+                                    }
                                     .van-slider__button-wrapper{
                                         bottom: 0;
                                         top: initial;

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

@@ -10,6 +10,8 @@ import Recommendation from "../../custom-plugins/helper-model/recommendation";
 import { resetRenderMusicScore } from "/src/view/music-score";
 import ScreenModel from "../../custom-plugins/helper-model/screen-model";
 import { getQuery } from "/src/utils/queryString";
+import { reCheckDelay } from "/src/page-instrument/evaluat-model"
+import { audioData, changeMingSongType } from "/src/view/audio-list"
 
 export default defineComponent({
 	name: "settting",
@@ -35,7 +37,7 @@ export default defineComponent({
         const formatterTimeMs = (value: any) => value = String(Math.min(3000, value));
 
         const notationList = computed(() => {
-            const list = state.enableNotation ? [{name:'五线谱',value:'staff'},{name:'首调',value:'firstTone'},{name:'固定谱',value:'fixedTone'}] : [{name:'首调',value:'firstTone'},{name:'固定谱',value:'fixedTone'}];
+            const list = state.enableNotation ? [{name:'五线谱',value:'staff'},{name:'首调',value:'firstTone'},{name:'固定调',value:'fixedTone'}] : [{name:'首调',value:'firstTone'},{name:'固定调',value:'fixedTone'}];
             return list;
         });
 
@@ -48,7 +50,7 @@ export default defineComponent({
                 <div class={styles.content}>
                     <div class={styles.conBox}>
                         {
-                            state.isShowFingering && state.fingeringInfo.name && ["practise", "follow"].includes(state.modeType) &&
+                            state.isShowFingering && state.fingeringInfo.name && ["practise", "follow"].includes(state.modeType) && state.playType === "play" &&
                                 <div class={styles.cellBox}>
                                 <div class={styles.tit}>指法</div>
                                     <Switch v-model={state.setting.displayFingering}></Switch>
@@ -60,12 +62,36 @@ export default defineComponent({
                                     <div class={styles.tit}>循环播放</div>
                                     <Switch v-model={state.setting.repeatAutoPlay}></Switch>
                                 </div>
+                        }                        
+                        {
+                            state.modeType === 'practise' && state.mingSong && state.mingSongGirl &&
+                            <div class={styles.cellBox}>
+                                <div class={styles.tit}>唱名类型</div>
+                                <div class={styles.radioBox}>
+                                    {
+                                        [{name:'男生',value:1}, {name:'女生',value:0}].map(item=>{
+                                            return <div class={ audioData.mingSongType===item.value && styles.active } onClick={ ()=>{ 
+                                                if(audioData.mingSongType === item.value){
+                                                    return
+                                                }
+                                                audioData.mingSongType = item.value as 0|1
+                                                changeMingSongType()
+                                            } }>{item.name}</div>
+                                        })
+                                    }
+                                </div>
+                            </div>                     
                         }
                         {
-                            state.isSingleLine && state.modeType === "practise" &&
+                            state.isSingleLine && state.modeType === "practise" && !state.isCombineRender && !state.isPercussion && 
                                 <div class={styles.cellBox}>
                                 <div class={styles.tit}>旋律线</div>
-                                    <Switch v-model={smoothAnimationState.isShow.value}></Switch>
+                                    <Switch 
+                                        v-model={smoothAnimationState.isShow.value}
+                                        onChange={(value) => {
+                                            state.melodyLine = value
+                                        }}                                        
+                                    ></Switch>
                                 </div>   
                         }                           
                         {
@@ -88,7 +114,10 @@ export default defineComponent({
                                 }                     
                                 <div class={styles.cellBox}>
                                     <div class={styles.tit}>延迟检测</div>
-                                    <Switch v-model={state.setting.soundEffect}></Switch>
+                                    {/* <Switch v-model={state.setting.soundEffect}></Switch> */}
+                                    <div class={styles.titbtn} onClick={() => {
+                                        reCheckDelay();
+                                    }}>重新检测</div>
                                 </div> 
                                 <div class={[styles.cellBox, state.setting.camera && styles.isCamera]}>
                                     <div class={styles.tit}>摄像头</div>
@@ -153,10 +182,16 @@ export default defineComponent({
                                     {
                                         [{name:'单行谱',value:true},{name:'多行谱',value:false}].map(item=>{
                                             return <div class={ state.isSingleLine===item.value && styles.active } onClick={ ()=>{ 
+                                                if(state.isSingleLine === item.value){
+                                                    return
+                                                }
+                                                headTopData.settingMode = false
                                                 state.isSingleLine = item.value 
                                                 // resetRenderMusicScore(state.musicRenderType)
-                                                headTopData.settingMode = false
-                                                refreshMusicSvg();
+                                                const _time = setTimeout(() => {
+                                                    clearTimeout(_time)
+                                                    refreshMusicSvg();
+                                                }, 100);
                                                 // musicScoreRef.value?.refreshMusicScore()
                                             } }>{item.name}</div>
                                         })
@@ -172,10 +207,16 @@ export default defineComponent({
                                     {
                                         notationList.value.map(item=>{
                                             return <div class={ state.musicRenderType===item.value && styles.active } onClick={ ()=>{ 
+                                                if(state.musicRenderType === item.value){
+                                                    return
+                                                }
+                                                headTopData.settingMode = false
                                                 state.musicRenderType = item.value as any
                                                 // resetRenderMusicScore(state.musicRenderType)
-                                                headTopData.settingMode = false
-                                                refreshMusicSvg();
+                                                const _time = setTimeout(() => {
+                                                    clearTimeout(_time)
+                                                    refreshMusicSvg();
+                                                }, 100);
                                             } }>{item.name}</div>
                                         })
                                     }

+ 53 - 3
src/page-instrument/header-top/speed/index.module.less

@@ -9,13 +9,26 @@
             box-shadow: 0px 4px 0px 0px #5EA2B9;
             .conBox{
                 background: #E3F3F5;
+                .speedSel{
+                    border-color: #CFE6EC ;
+                }
             }
         }
     }
     &.evaluating{
         .head{
+            width: 394px;
+            height: 62px;
+            margin-bottom: -3px;
             background: url("../image/headImg2.png") no-repeat;
             background-size: 100% 100%; 
+            .headTit{
+                bottom: 11px;
+            }
+            .closeImg{
+                top: 2px;
+                right: -26px;
+            }
         }
         .content{
             background: #B0CDFF;
@@ -97,6 +110,28 @@
                                 background: linear-gradient( 270deg, #7ADEFF 0%, #29A9FF 100%);
                                 box-shadow: inset 1px 0px 5px 0px rgba(150,254,255,0.79);
                                 border: 1px solid #4A91D4;
+                                &::after{
+                                    position: absolute;
+                                    content: "";
+                                    left: 4px;
+                                    top: 2px;
+                                    width: 100%;
+                                    height: 1px;
+                                    background: #FFFFFF;
+                                    border-radius: 1px;
+                                    filter: blur(1px);
+                                }
+                                &::before{
+                                    position: absolute;
+                                    content: "";
+                                    left: 2px;
+                                    top: 1px;
+                                    width: 4px;
+                                    height: 4px;
+                                    background: url("../image/gg.png") no-repeat;
+                                    background-size: 100% 100%;
+                                    transform: translate(-20%,-20%);
+                                }
                                 .van-slider__button-wrapper{
                                     bottom: 0;
                                     top: initial;
@@ -122,7 +157,7 @@
                             padding-top: 3px;
                         }
                         .speedBtn{
-                            width: 16px;
+                            width: 20px;
                             height: 30px;
                             background: url("../image/speedBtn.png") no-repeat;
                             background-size: 100% 100%;
@@ -137,19 +172,24 @@
                 display: flex;
                 justify-content: space-between;
                 & > div{
-                    padding: 3px 13px;
+                    padding: 6px 13px;
                     background: #FFFFFF;
                     border-radius: 14px;
                     font-weight: 400;
                     font-size: 13px;
                     color: rgba(0,0,0,0.6);
-                    line-height: 18px;
+                    line-height: 1;
                     cursor: pointer;
                     &:active{
                         background: linear-gradient( 131deg, #44CAFF 0%, #259CFE 100%);
+                        color: #fff;
                     }
                 }
             }
+            .disableSpend {
+                opacity: 0.4;
+                pointer-events: none;
+            }
             .metronome{
                 margin-top: 18px;
                 display: flex;
@@ -172,6 +212,16 @@
                         }
                     }
                 }
+                .switchLoading {
+                    :global {
+                        .van-switch__node{
+                            display: block;
+                            width: 16px;
+                            height: 16px;
+                            // top: 3px;
+                        }
+                    }
+                }
             }
         }
     }

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

@@ -3,14 +3,19 @@ import { Switch, Slider } from "vant";
 import styles from "./index.module.less"
 import { headData } from "../index" 
 import { headImg } from "../image";
-import state, { handleSetSpeed } from "../../../state";
+import state, { handleSetSpeed, resetBaseRate } from "../../../state";
 import { metronomeData } from "../../../helpers/metronome"; 
+import { getQuery } from "/src/utils/queryString";
+import { api_createMusicPlayer, api_updateMusicPlayer, api_checkSocketStatus } from "/src/helpers/communication";
+import { storeData } from "/src/store";
+import { data as workData } from "/src/page-instrument/custom-plugins/work-index";
 
 export default defineComponent({
 	name: "speed",
 	setup() {
 		const speed = ref(state.speed);
-
+		const switchLoading = ref(false);
+		const query: any = getQuery();
 		const minusSpeed = () => {
 			let canSpeed = Math.max(speed.value - 1, 45);
 			canSpeed = Math.min(canSpeed, 270);
@@ -22,11 +27,20 @@ export default defineComponent({
 			canSpeed = Math.max(canSpeed, 45);
 			speed.value = canSpeed;
 		};
+		// 重置当前小节的速度
+		const resetCurrentSpeed = () => {
+			resetBaseRate(state.activeNoteIndex);
+		};
 		watch(
 			() => speed.value,
 			() => {
 				// handleSetSpeed(speed.value);
-				state.speed = speed.value;
+				state.speed = Math.floor(speed.value);
+				// handleSetSpeed(speed.value);
+				if (state.playState === 'paused') {
+					const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
+					state.basePlayRate = currentItem?.measureSpeed ? state.speed / currentItem.measureSpeed : state.speed / state.originSpeed;
+				}
 			}
 		);
 		watch(
@@ -45,6 +59,51 @@ export default defineComponent({
 				metronomeData.disable = !val
 			}
 		})
+		// 切换节拍器
+		const toggleSwitch = async (res: any) => {
+			switchLoading.value = true;
+			try {
+			  if (storeData.isApp && state.enableEvaluation) {
+				// 加载弹窗的开始时间
+				let startTime = +new Date();
+				state.loadingText = '节拍器准备中,请稍等…'
+				state.isLoading = true;
+				const targetSrc = res ? state.beatSong.accompany || state.beatSong.music : state.accompany || state.music;
+				const resData = await api_updateMusicPlayer({
+					musicSrc: targetSrc || state.accompany || state.music, // 曲谱音频url,有可能含节拍器的音频不存在
+					tuneSrc: "https://oss.dayaedu.com/MECMP/1722593665681.mp3", //效音音频url
+					checkFrequence: 496,
+				})
+				// console.log('切换节拍器音频',resData)
+				if (resData?.content) {
+					// 返回结果的时间
+					let intervalTime = +new Date() - startTime;
+					// 超过1秒直接关闭加载弹窗,小于1秒则最短显示1秒加载弹窗
+					if (intervalTime >= 1000) {
+						state.isLoading = false;
+						metronomeDisable.value = res;
+						switchLoading.value = false;
+					} else {
+						const continueTime = 1000 - intervalTime;
+						setTimeout(() => {
+							state.isLoading = false;
+							metronomeDisable.value = res;
+							switchLoading.value = false;
+						}, continueTime);
+					}
+				}
+				// api_checkSocketStatus();
+			  } else {
+				metronomeDisable.value = res;
+				switchLoading.value = false;
+			  }
+			} catch (error) {
+			  console.log(error)
+			} finally {
+				state.isLoading = false;
+				switchLoading.value = false;
+			}
+		};
 		return () => (
 			<div class={[styles.speedContainer, styles[state.modeType]]}>
 				<div class={styles.head}>
@@ -54,14 +113,14 @@ export default defineComponent({
 				<div class={styles.content}>
 					<div class={styles.conBox}>
 						<div class={styles.tit}>速度</div>
-						<div class={styles.spendCon}>
+						<div class={[styles.spendCon, workData.trainingType === "PRACTICE" && styles.disableSpend]}>
 							<img src={headImg("cutImg.png")} class={[styles.btn]} onClick={minusSpeed} />
 							<div class={styles.sliderCon}>
-								<Slider class={styles.slider} max={270} min={45} v-model={speed.value}>
+								<Slider class={styles.slider} max={270} min={speed.value < 45 ? speed.value : 45} v-model={speed.value}>
 									{{
 										button: () => 
 										<div class={styles.customButton}>
-											<div class={styles.speedVal}>{ speed.value }</div>
+											<div class={styles.speedVal}>{ Math.floor(speed.value) }</div>
 											<div class={styles.speedBtn}></div>
 										</div>
 									}}
@@ -69,8 +128,8 @@ export default defineComponent({
 							</div>
 							<img src={headImg("addImg.png")} class={[styles.btn]} onClick={plusSpeed} />	
 						</div>
-						<div class={styles.speedSel}>
-							<div onClick={()=>{ speed.value = state.originSpeed }}>原速</div>
+						<div class={[styles.speedSel, workData.trainingType === "PRACTICE" && styles.disableSpend]}>
+							<div onClick={resetCurrentSpeed}>原速</div>
 							<div onClick={()=>{ speed.value = 70 }}>70</div>
 							<div onClick={()=>{ speed.value = 80 }}>80</div>
 							<div onClick={()=>{ speed.value = 90 }}>90</div>
@@ -79,7 +138,12 @@ export default defineComponent({
 						</div>
 						<div class={styles.metronome}>
 							<div class={styles.tit}>节拍器</div>
-							<Switch v-model={metronomeDisable.value}></Switch>
+							<Switch 
+								class={switchLoading.value ? styles.switchLoading : ''}
+								v-model:modelValue={metronomeDisable.value} 
+								loading={switchLoading.value}
+								onChange={toggleSwitch}			
+							></Switch>
 						</div>
 					</div>
 				</div>

+ 11 - 1
src/page-instrument/simple-detail/index.module.less

@@ -1,3 +1,7 @@
+body {
+    overflow: hidden;
+}
+
 .skeleton {
     position: fixed;
     left: 0;
@@ -17,7 +21,13 @@
     --header-height: 62px;
     // background: var(--container-background);
     background: transparent;
-
+    position: relative;
+    .mask{
+        position: absolute;
+        z-index: 6;
+        width: 100%;
+        height: 100%;
+    }
     .container {
         margin: 0;
         border-radius: 10px;

+ 38 - 10
src/page-instrument/simple-detail/index.tsx

@@ -1,5 +1,5 @@
-import { defineComponent, onMounted, onUnmounted, reactive } from "vue";
-import state, { getMusicDetail, handleSetSpeed, addNoteBBox, getNote, gotoNext } from "/src/state";
+import { defineComponent, onMounted, onUnmounted, reactive, nextTick } from "vue";
+import state, { getMusicDetail, handleSetSpeed, addNoteBBox, getNote, gotoNext, fillWordColor, moveSvgDom } from "/src/state";
 import MusicScore from "../../view/music-score";
 import styles from "./index.module.less";
 import { getQuery } from "/src/utils/queryString";
@@ -37,25 +37,48 @@ export default defineComponent({
 			}
 			// 暂停播放
 			if (resInfo?.api === "api_paused") {
-				console.log('暂停播放')
+				const currentTime = resInfo?.content?.currentTime
+				console.log('暂停播放',currentTime)
 				state.playState = 'paused';
+				if (currentTime === 0) {
+					// 坐标和小节都改为初始值
+					setTimeout(() => {
+						state.activeNoteIndex = 0
+						state.activeMeasureIndex = state.times[0].MeasureNumberXML;
+						handlePlaying(true);
+					}, 200);
+				}
 			}
 			// 暂停状态下,拖动进度
 			if (resInfo?.api === "api_updateProgress") {
-				console.log('拖动的进度')
+				console.log('拖动的进度',state.playState)
 				if (state.playState === 'paused') {
-					detailData.currentTime = resInfo?.content?.currentTime ? resInfo?.content?.currentTime : detailData.currentTime;
+					detailData.currentTime = resInfo?.content?.currentTime ?? detailData.currentTime;
+					// 坐标和小节都改为初始值
+					state.activeNoteIndex = 0
+					state.activeMeasureIndex = state.times[0].MeasureNumberXML;
 					handlePlaying(true);
 				}
 			}
 			// 播放进度
 			if (resInfo?.api === "api_playProgress") {
 				// console.log('播放进度',resInfo)
-				if (resInfo?.content?.currentTime) {
-					if (resInfo?.content?.currentTime < detailData.currentTime) {
+				const currentTime = resInfo?.content?.currentTime
+				if (currentTime) {
+					if (currentTime < detailData.currentTime) {
+						// 坐标和小节都改为初始值
 						state.activeNoteIndex = 0
+						let item = getNote(currentTime) || state.times[0];
+						if(item.i === state.activeNoteIndex){
+							state.activeMeasureIndex = item.MeasureNumberXML;
+						}
+						// 当拖动到MP3节拍器时候 需要手动移动到当前为止
+						const fixtime = state.times[0].fixtime
+						if(currentTime <= fixtime){
+							moveSvgDom(true)
+						}
 					}
-					detailData.currentTime = resInfo?.content?.currentTime
+					detailData.currentTime = currentTime
 				}
 			}
 		};
@@ -101,7 +124,11 @@ export default defineComponent({
 			setCustomGradual();
 			setCustomNoteRealValue();
 			state.times = formateTimes(osmd);
-			console.log("🚀 ~ state.times:", state.times, state.subjectId, state);
+			console.log("🚀 ~ state.times:", state.times, state);
+			nextTick(() => {
+				state.activeMeasureIndex = state.times[0].MeasureNumberXML;
+				fillWordColor();
+			})
 			// 音符添加位置信息bbox
 			addNoteBBox(state.times);
 			// 一行谱创建 动画
@@ -130,7 +157,7 @@ export default defineComponent({
 			//detailData.currentTime += 0.03
 			const currentTime = detailData.currentTime;
 			// console.log('👀~播放进度',currentTime)
-			let item = getNote(currentTime);
+			let item = getNote(currentTime) || state.times[0];
 			if (item) {
 				gotoNext(item, skipNote);
 			}
@@ -166,6 +193,7 @@ export default defineComponent({
 
 		return () => (
 			<div class={styles.detail}>
+				<div class={styles.mask}></div>
 				<div id="scrollContainer" class={[styles.container, "hideCursor"]}>
 					{/* 曲谱渲染 */}
 					{!detailData.isLoading && 

BIN
src/page-instrument/view-detail/images/bg1.png


BIN
src/page-instrument/view-detail/images/bg2.png


BIN
src/page-instrument/view-detail/images/bg2_left_zs.png


BIN
src/page-instrument/view-detail/images/bg2_right_zs.png


BIN
src/page-instrument/view-detail/images/bg3.png


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff