Ver código fonte

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

TIANYONG 1 ano atrás
pai
commit
743cea6a1c
100 arquivos alterados com 1726 adições e 494 exclusões
  1. 1 1
      colexiu-report.html
  2. 8 9
      dist/colexiu.html
  3. 20 8
      dist/flexible.js
  4. 12 12
      dist/index.html
  5. 10 12
      dist/instrument.html
  6. 11 13
      dist/orchestra.html
  7. 7 7
      dist/report-share.html
  8. 1 1
      index.html
  9. 1 1
      instrument.html
  10. 1 1
      orchestra.html
  11. 30 0
      package-lock.json
  12. 1 0
      package.json
  13. 20 8
      public/flexible.js
  14. 1 1
      public/share-evaluating/index.html
  15. 2 2
      public/share/index.html
  16. BIN
      public/soundfonts/whistling/A4.mp3
  17. BIN
      public/soundfonts/whistling/A5.mp3
  18. BIN
      public/soundfonts/whistling/Ab5.mp3
  19. BIN
      public/soundfonts/whistling/B4.mp3
  20. BIN
      public/soundfonts/whistling/B5.mp3
  21. BIN
      public/soundfonts/whistling/Bb4.mp3
  22. BIN
      public/soundfonts/whistling/Bb5.mp3
  23. BIN
      public/soundfonts/whistling/C5.mp3
  24. BIN
      public/soundfonts/whistling/C6.mp3
  25. BIN
      public/soundfonts/whistling/D5.mp3
  26. BIN
      public/soundfonts/whistling/D6.mp3
  27. BIN
      public/soundfonts/whistling/Db5.mp3
  28. BIN
      public/soundfonts/whistling/Db6.mp3
  29. BIN
      public/soundfonts/whistling/E5.mp3
  30. BIN
      public/soundfonts/whistling/E6.mp3
  31. BIN
      public/soundfonts/whistling/Eb5.mp3
  32. BIN
      public/soundfonts/whistling/Eb6.mp3
  33. BIN
      public/soundfonts/whistling/F5.mp3
  34. BIN
      public/soundfonts/whistling/F6.mp3
  35. BIN
      public/soundfonts/whistling/G5.mp3
  36. BIN
      public/soundfonts/whistling/Gb5.mp3
  37. 1 1
      report-share.html
  38. BIN
      src/assets/tick.mp3
  39. BIN
      src/assets/tick.wav
  40. BIN
      src/assets/tock.mp3
  41. BIN
      src/assets/tock.wav
  42. 6 1
      src/helpers/communication.ts
  43. 120 4
      src/helpers/customMusicScore.ts
  44. 296 14
      src/helpers/formateMusic.ts
  45. 25 3
      src/helpers/metronome.ts
  46. 68 0
      src/helpers/oss-file-upload.ts
  47. 59 0
      src/hooks/useDrag/dragbom.tsx
  48. BIN
      src/hooks/useDrag/img/left.png
  49. BIN
      src/hooks/useDrag/img/modalDragBg.png
  50. BIN
      src/hooks/useDrag/img/modalDragBg2.png
  51. BIN
      src/hooks/useDrag/img/modalDragBgLeft.png
  52. BIN
      src/hooks/useDrag/img/modalDragBgRight.png
  53. BIN
      src/hooks/useDrag/img/modalDragDone.png
  54. BIN
      src/hooks/useDrag/img/right.png
  55. 88 0
      src/hooks/useDrag/index.module.less
  56. 163 0
      src/hooks/useDrag/index.ts
  57. 39 0
      src/hooks/useDrag/useDragGuidance.ts
  58. 14 13
      src/page-colexiu/detail/index.tsx
  59. 1 1
      src/page-colexiu/header-top/image/arrow.svg
  60. 14 14
      src/page-gym/detail/index.tsx
  61. 1 1
      src/page-gym/evaluat-model/evaluat-share/index.tsx
  62. 1 1
      src/page-gym/evaluat-model/icons/arrow-left-background.svg
  63. 1 1
      src/page-gym/header-top/image/arrow.svg
  64. 1 1
      src/page-gym/header-top/image/icon-back.svg
  65. 1 1
      src/page-gym/header-top/image/menu.svg
  66. 1 1
      src/page-gym/header-top/image/replay.svg
  67. 1 1
      src/page-gym/header-top/image/section0.svg
  68. 1 1
      src/page-gym/header-top/image/section1.svg
  69. 1 1
      src/page-gym/header-top/image/section2.svg
  70. 1 1
      src/page-gym/header-top/image/speed.svg
  71. 1 1
      src/page-gym/musci-list/index.tsx
  72. 1 1
      src/page-gym/musci-list/vipModel/index.tsx
  73. 20 7
      src/page-instrument/App.tsx
  74. 19 2
      src/page-instrument/api.ts
  75. 41 0
      src/page-instrument/component/authorName/index.module.less
  76. 42 0
      src/page-instrument/component/authorName/index.tsx
  77. 80 96
      src/page-instrument/component/mode-type-mode/index.tsx
  78. 3 0
      src/page-instrument/component/the-comfirm/index.module.less
  79. 3 1
      src/page-instrument/component/the-comfirm/index.tsx
  80. BIN
      src/page-instrument/component/the-music-list/icon_menu.png
  81. BIN
      src/page-instrument/component/the-music-list/imgs/bg.png
  82. BIN
      src/page-instrument/component/the-music-list/imgs/huo.png
  83. BIN
      src/page-instrument/component/the-music-list/imgs/icon-music-vip.png
  84. BIN
      src/page-instrument/component/the-music-list/imgs/qtqm.png
  85. BIN
      src/page-instrument/component/the-music-list/imgs/qtqmAct.png
  86. BIN
      src/page-instrument/component/the-music-list/imgs/searImg.png
  87. BIN
      src/page-instrument/component/the-music-list/imgs/zjlx.png
  88. BIN
      src/page-instrument/component/the-music-list/imgs/zjlxAct.png
  89. 184 73
      src/page-instrument/component/the-music-list/index.module.less
  90. 11 9
      src/page-instrument/component/the-music-list/index.tsx
  91. 119 94
      src/page-instrument/component/the-music-list/list.tsx
  92. BIN
      src/page-instrument/custom-plugins/guide-page/images/pc_teacherBottom1.png
  93. BIN
      src/page-instrument/custom-plugins/guide-page/images/pc_teacherBottom2.png
  94. BIN
      src/page-instrument/custom-plugins/guide-page/images/pc_teacherBottom3.png
  95. BIN
      src/page-instrument/custom-plugins/guide-page/images/pc_teacherBottom4.png
  96. BIN
      src/page-instrument/custom-plugins/guide-page/images/pc_teacherBottom5.png
  97. BIN
      src/page-instrument/custom-plugins/guide-page/images/pc_teacherBottom6.png
  98. 57 15
      src/page-instrument/custom-plugins/guide-page/student-bottom.tsx
  99. 62 48
      src/page-instrument/custom-plugins/guide-page/student-top.tsx
  100. 53 10
      src/page-instrument/custom-plugins/guide-page/teacher-bootom.tsx

+ 1 - 1
colexiu-report.html

@@ -6,7 +6,7 @@
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
   <meta name="viewport"
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
-  <title>管乐团云练</title>
+  <title>管乐团云练</title>
   <!-- <link rel="icon" href="/favicon.ico" /> -->
   <script src="/flexible.js" charset="UTF-8"></script>
   <script src="/helpers/lottie.min.js" charset="UTF-8"></script>

+ 8 - 9
dist/colexiu.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-c6964e82.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-1a2e4f1c.js"></script>
 
   <meta charset="UTF-8" />
   <link rel="icon" type="image/svg+xml" href="./vite.svg" />
@@ -40,12 +40,11 @@
       },
     })
   </script>
-  <script type="module" crossorigin src="./js/colexiu-9c46ee5b.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-20c0b3e5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d70569b1.js">
-  <link rel="modulepreload" crossorigin href="./js/index-785537cc.js">
-  <link rel="modulepreload" crossorigin href="./js/index-9ce74d33.js">
-  <link rel="stylesheet" href="./css/index-5cdcded6.css">
+  <script type="module" crossorigin src="./js/colexiu-4a63d64c.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-c987494a.js">
+  <link rel="modulepreload" crossorigin href="./js/index-d8f93bbd.js">
+  <link rel="modulepreload" crossorigin href="./js/index-666ce298.js">
+  <link rel="stylesheet" href="./css/index-33f3c513.css">
   <link rel="stylesheet" href="./css/index-d42b0794.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>
@@ -57,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-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/colexiu-legacy-5bc8e41d.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-b3b6467d.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/colexiu-legacy-9355aa1b.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 20 - 8
dist/flexible.js

@@ -1,13 +1,25 @@
 ! function (a, b) {
   function c() {
-    var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
-    var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
-    var b = width > height ? height : width;
-    b / i > 640 && (b = 640 * i);
-    b / i < 375 && (b = 375 * i);
-    var c = b / 10;
-    f.style.fontSize = c + "px", k.rem = a.rem = c
-    window.fontSize = c
+    try {
+      var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
+      var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
+      var b = width > height ? height : width;
+      b / i > 640 && (b = 640 * i);
+      b / i < 375 && (b = 375 * i);
+      var c = b / 10;
+      // 老师端,fontsize计算
+      // if (window.location.search.includes('platform=pc') || window.location.search.includes('platform=PC')) {
+      //   c = width / 21.7;
+      // }
+      // console.log('fontSize:',c,'屏幕宽度:',width,'老师端:',window.location.search.includes('platform=pc'))
+      c = c || 64;
+      f.style.fontSize = c + "px", k.rem = a.rem = c
+      window.fontSize = c
+    } catch (error) {
+      f.style.fontSize = 64 + "px"
+      window.fontSize = 64
+    }
+
   }
   var d, e = a.document,
     f = e.documentElement,

+ 12 - 12
dist/index.html

@@ -2,14 +2,14 @@
 <html lang="ZH-cn">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-c6964e82.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-1a2e4f1c.js"></script>
 
   <meta charset="UTF-8">
   <link rel="icon" href="./favicon.ico" />
   <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
   <meta http-equiv="Cache-control" content="no-cache">
   <meta http-equiv="Cache" content="no-cache">
-  <title>云练</title>
+  <title>云练</title>
   <script src="./flexible.js" charset="UTF-8"></script>
   <style>
     #loading {
@@ -75,16 +75,16 @@
       }
     })
   </script>
-  <script type="module" crossorigin src="./js/gym-30e70e97.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-20c0b3e5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d70569b1.js">
-  <link rel="modulepreload" crossorigin href="./js/instruments-cf6be8a5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-f4081c3c.js">
-  <link rel="modulepreload" crossorigin href="./js/index-9ce74d33.js">
+  <script type="module" crossorigin src="./js/gym-65bdffe8.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-c987494a.js">
+  <link rel="modulepreload" crossorigin href="./js/index-948cb66f.js">
+  <link rel="modulepreload" crossorigin href="./js/index-27021a09.js">
+  <link rel="modulepreload" crossorigin href="./js/index-d8f93bbd.js">
+  <link rel="modulepreload" crossorigin href="./js/index-666ce298.js">
   <link rel="modulepreload" crossorigin href="./js/plyr.min-c8c2777b.js">
-  <link rel="stylesheet" href="./css/index-5cdcded6.css">
-  <link rel="stylesheet" href="./css/index-d42b0794.css">
+  <link rel="stylesheet" href="./css/index-33f3c513.css">
   <link rel="stylesheet" href="./css/index-85f95688.css">
+  <link rel="stylesheet" href="./css/index-d42b0794.css">
   <link rel="stylesheet" href="./css/plyr-ad8ef5ae.css">
   <link rel="stylesheet" href="./css/index-171cd132.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
@@ -100,8 +100,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-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/gym-legacy-7c4e7ebf.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-b3b6467d.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/gym-legacy-d67a9763.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 10 - 12
dist/instrument.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-c6964e82.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-1a2e4f1c.js"></script>
 
   <meta charset="UTF-8" />
   <meta name="viewport"
@@ -40,15 +40,13 @@
       })
     }
   </script>
-  <script type="module" crossorigin src="./js/instrument-37bde527.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-20c0b3e5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d70569b1.js">
-  <link rel="modulepreload" crossorigin href="./js/index-785537cc.js">
-  <link rel="modulepreload" crossorigin href="./js/instruments-cf6be8a5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-342b6cb7.js">
-  <link rel="stylesheet" href="./css/index-5cdcded6.css">
+  <script type="module" crossorigin src="./js/instrument-ff82a3ab.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-c987494a.js">
+  <link rel="modulepreload" crossorigin href="./js/index-666ce298.js">
+  <link rel="modulepreload" crossorigin href="./js/index-948cb66f.js">
+  <link rel="stylesheet" href="./css/index-33f3c513.css">
   <link rel="stylesheet" href="./css/index-d42b0794.css">
-  <link rel="stylesheet" href="./css/instrument-6354a91f.css">
+  <link rel="stylesheet" href="./css/instrument-d78850b6.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>
@@ -58,7 +56,7 @@
   <img id="loading" class="show" src="./loading.svg" alt="loading" />
   <script>
     // 处理课堂乐器老师端打开听音练习时去掉加载动画
-    if (location.href.indexOf('view-figner') >= 0 && location.href.indexOf('platform=pc') >= 0) {
+    if (location.href.indexOf('view-figner') >= 0 && location.href.indexOf('platform=pc') >= 0 && location.href.indexOf('linkSource=class') < 0) {
       var _loading = document.getElementById("loading");
       _loading && document.body.removeChild(_loading);
     }
@@ -66,8 +64,8 @@
 
   
   <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-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-43629e0f.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-b3b6467d.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-6acd9b82.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 11 - 13
dist/orchestra.html

@@ -2,13 +2,13 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-c6964e82.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-1a2e4f1c.js"></script>
 
   <meta charset="UTF-8" />
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
   <meta name="viewport"
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
-  <title>管乐团云练</title>
+  <title>管乐团云练</title>
   <script>
     function _postMessage(data, callback) {
       const instance = window.ORCHESTRA || (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.ORCHESTRA)
@@ -41,16 +41,14 @@
       transition: opacity .3s;
     }
   </style>
-  <script type="module" crossorigin src="./js/orchestra-a8f204b4.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-20c0b3e5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d70569b1.js">
-  <link rel="modulepreload" crossorigin href="./js/index-785537cc.js">
-  <link rel="modulepreload" crossorigin href="./js/index-f4081c3c.js">
-  <link rel="modulepreload" crossorigin href="./js/index-9ce74d33.js">
-  <link rel="modulepreload" crossorigin href="./js/index-342b6cb7.js">
-  <link rel="stylesheet" href="./css/index-5cdcded6.css">
-  <link rel="stylesheet" href="./css/index-d42b0794.css">
+  <script type="module" crossorigin src="./js/orchestra-9b200011.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-c987494a.js">
+  <link rel="modulepreload" crossorigin href="./js/index-27021a09.js">
+  <link rel="modulepreload" crossorigin href="./js/index-d8f93bbd.js">
+  <link rel="modulepreload" crossorigin href="./js/index-666ce298.js">
+  <link rel="stylesheet" href="./css/index-33f3c513.css">
   <link rel="stylesheet" href="./css/index-85f95688.css">
+  <link rel="stylesheet" href="./css/index-d42b0794.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>
   <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>
@@ -72,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-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/orchestra-legacy-48272a5d.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-b3b6467d.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/orchestra-legacy-4ed40420.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 7 - 7
dist/report-share.html

@@ -2,13 +2,13 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-c6964e82.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-1a2e4f1c.js"></script>
 
   <meta charset="UTF-8" />
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
   <meta name="viewport"
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
-  <title>管乐团云练</title>
+  <title>管乐团云练</title>
   <!-- <link rel="icon" href="/favicon.ico" /> -->
   <script src="./flexible.js" charset="UTF-8"></script>
   <script src="./helpers/lottie.min.js" charset="UTF-8"></script>
@@ -25,10 +25,10 @@
       transition: opacity .3s;
     }
   </style>
-  <script type="module" crossorigin src="./js/report-share-3645a89c.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-20c0b3e5.js">
+  <script type="module" crossorigin src="./js/report-share-ef089666.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-c987494a.js">
   <link rel="modulepreload" crossorigin href="./js/plyr.min-c8c2777b.js">
-  <link rel="stylesheet" href="./css/index-5cdcded6.css">
+  <link rel="stylesheet" href="./css/index-33f3c513.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 +51,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-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/report-share-legacy-ff2275ea.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-b3b6467d.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/report-share-legacy-1420c584.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 1 - 1
index.html

@@ -7,7 +7,7 @@
   <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
   <meta http-equiv="Cache-control" content="no-cache">
   <meta http-equiv="Cache" content="no-cache">
-  <title>云练</title>
+  <title>云练</title>
   <script src="/flexible.js" charset="UTF-8"></script>
   <style>
     #loading {

+ 1 - 1
instrument.html

@@ -45,7 +45,7 @@
   <img id="loading" class="show" src="/loading.svg" alt="loading" />
   <script>
     // 处理课堂乐器老师端打开听音练习时去掉加载动画
-    if (location.href.indexOf('view-figner') >= 0 && location.href.indexOf('platform=pc') >= 0) {
+    if (location.href.indexOf('view-figner') >= 0 && location.href.indexOf('platform=pc') >= 0 && location.href.indexOf('linkSource=class') < 0) {
       var _loading = document.getElementById("loading");
       _loading && document.body.removeChild(_loading);
     }

+ 1 - 1
orchestra.html

@@ -6,7 +6,7 @@
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
   <meta name="viewport"
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
-  <title>管乐团云练</title>
+  <title>管乐团云练</title>
   <script>
     function _postMessage(data, callback) {
       const instance = window.ORCHESTRA || (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.ORCHESTRA)

+ 30 - 0
package-lock.json

@@ -12,6 +12,7 @@
         "@varlet/ui": "^2.9.5",
         "clean-deep": "^3.4.0",
         "consola": "^2.15.3",
+        "cos-js-sdk-v5": "^1.4.20",
         "dayjs": "^1.11.7",
         "eventemitter3": "^5.0.0",
         "hammerjs": "^2.0.8",
@@ -2752,6 +2753,14 @@
       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
       "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
     },
+    "node_modules/@xmldom/xmldom": {
+      "version": "0.8.10",
+      "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+      "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/acorn": {
       "version": "8.8.2",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@@ -3210,6 +3219,14 @@
         "url": "https://opencollective.com/core-js"
       }
     },
+    "node_modules/cos-js-sdk-v5": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/cos-js-sdk-v5/-/cos-js-sdk-v5-1.8.1.tgz",
+      "integrity": "sha512-crsdrcDSRJqdniG61JiQCEA4FIUMuvqNohRCIONrHGd23xvykZs8uySLHmvAKDieAYRT+P72F/Ng7kjycW2b1w==",
+      "dependencies": {
+        "@xmldom/xmldom": "^0.8.6"
+      }
+    },
     "node_modules/css-line-break": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
@@ -7549,6 +7566,11 @@
       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
       "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
     },
+    "@xmldom/xmldom": {
+      "version": "0.8.10",
+      "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+      "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw=="
+    },
     "acorn": {
       "version": "8.8.2",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@@ -7897,6 +7919,14 @@
         "browserslist": "^4.21.5"
       }
     },
+    "cos-js-sdk-v5": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/cos-js-sdk-v5/-/cos-js-sdk-v5-1.8.1.tgz",
+      "integrity": "sha512-crsdrcDSRJqdniG61JiQCEA4FIUMuvqNohRCIONrHGd23xvykZs8uySLHmvAKDieAYRT+P72F/Ng7kjycW2b1w==",
+      "requires": {
+        "@xmldom/xmldom": "^0.8.6"
+      }
+    },
     "css-line-break": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",

+ 1 - 0
package.json

@@ -12,6 +12,7 @@
     "@varlet/ui": "^2.9.5",
     "clean-deep": "^3.4.0",
     "consola": "^2.15.3",
+    "cos-js-sdk-v5": "^1.4.20",
     "dayjs": "^1.11.7",
     "eventemitter3": "^5.0.0",
     "hammerjs": "^2.0.8",

+ 20 - 8
public/flexible.js

@@ -1,13 +1,25 @@
 ! function (a, b) {
   function c() {
-    var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
-    var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
-    var b = width > height ? height : width;
-    b / i > 640 && (b = 640 * i);
-    b / i < 375 && (b = 375 * i);
-    var c = b / 10;
-    f.style.fontSize = c + "px", k.rem = a.rem = c
-    window.fontSize = c
+    try {
+      var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
+      var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
+      var b = width > height ? height : width;
+      b / i > 640 && (b = 640 * i);
+      b / i < 375 && (b = 375 * i);
+      var c = b / 10;
+      // 老师端,fontsize计算
+      // if (window.location.search.includes('platform=pc') || window.location.search.includes('platform=PC')) {
+      //   c = width / 21.7;
+      // }
+      // console.log('fontSize:',c,'屏幕宽度:',width,'老师端:',window.location.search.includes('platform=pc'))
+      c = c || 64;
+      f.style.fontSize = c + "px", k.rem = a.rem = c
+      window.fontSize = c
+    } catch (error) {
+      f.style.fontSize = 64 + "px"
+      window.fontSize = 64
+    }
+
   }
   var d, e = a.document,
     f = e.documentElement,

+ 1 - 1
public/share-evaluating/index.html

@@ -60,7 +60,7 @@
       <div class="app-info">
         <div class="app-title">管乐迷</div>
         <div class="app-desc">管乐学习的<span>不二选择</span></div>
-        <div class="app-subtitle">云练见证你的成长!</div>
+        <div class="app-subtitle">云练见证你的成长!</div>
       </div>
       <img src="./icons/qrcode.png"/>
     </div>

+ 2 - 2
public/share/index.html

@@ -30,7 +30,7 @@
       </div>
     </div>
     <p class="desc">
-      管乐迷使用云练可太好用了!演奏越来越好了,我的演奏快来看看吧!
+      管乐迷使用云练可太好用了!演奏越来越好了,我的演奏快来看看吧!
     </p>
     <div class="video-container">
       <video
@@ -48,7 +48,7 @@
       <div class="app-info">
         <div class="app-title">管乐迷</div>
         <div class="app-desc">管乐学习的<span>不二选择</span></div>
-        <div class="app-subtitle">云练见证你的成长!</div>
+        <div class="app-subtitle">云练见证你的成长!</div>
       </div>
       <img src="./icons/qrcode.png"/>
     </div>

BIN
public/soundfonts/whistling/A4.mp3


BIN
public/soundfonts/whistling/A5.mp3


BIN
public/soundfonts/whistling/Ab5.mp3


BIN
public/soundfonts/whistling/B4.mp3


BIN
public/soundfonts/whistling/B5.mp3


BIN
public/soundfonts/whistling/Bb4.mp3


BIN
public/soundfonts/whistling/Bb5.mp3


BIN
public/soundfonts/whistling/C5.mp3


BIN
public/soundfonts/whistling/C6.mp3


BIN
public/soundfonts/whistling/D5.mp3


BIN
public/soundfonts/whistling/D6.mp3


BIN
public/soundfonts/whistling/Db5.mp3


BIN
public/soundfonts/whistling/Db6.mp3


BIN
public/soundfonts/whistling/E5.mp3


BIN
public/soundfonts/whistling/E6.mp3


BIN
public/soundfonts/whistling/Eb5.mp3


BIN
public/soundfonts/whistling/Eb6.mp3


BIN
public/soundfonts/whistling/F5.mp3


BIN
public/soundfonts/whistling/F6.mp3


BIN
public/soundfonts/whistling/G5.mp3


BIN
public/soundfonts/whistling/Gb5.mp3


+ 1 - 1
report-share.html

@@ -6,7 +6,7 @@
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
   <meta name="viewport"
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
-  <title>管乐团云练</title>
+  <title>管乐团云练</title>
   <!-- <link rel="icon" href="/favicon.ico" /> -->
   <script src="/flexible.js" charset="UTF-8"></script>
   <script src="/helpers/lottie.min.js" charset="UTF-8"></script>

BIN
src/assets/tick.mp3


BIN
src/assets/tick.wav


BIN
src/assets/tock.mp3


BIN
src/assets/tock.wav


+ 6 - 1
src/helpers/communication.ts

@@ -187,7 +187,7 @@ export const api_cloudLoading = (show = false) => {
 	});
 };
 
-/** 销毁云练 */
+/** 销毁云练 */
 export const api_cloudDestroy = () => {
 	postMessage({
 		api: "cloudDestroy",
@@ -473,4 +473,9 @@ export const api_midiMicDelay = (content: any) => {
 		api: "proxyServiceMessage",
 		content,
 	});
+};
+
+/** 监听老师端,上课页面,功能按钮方向 */
+export const addImagePos = (callback: any) => {
+	listenerMessage("imagePos", callback);
 };

+ 120 - 4
src/helpers/customMusicScore.ts

@@ -82,7 +82,7 @@ export const resetGivenFormate = () => {
 	}
 	const tieList = musicList[state.cbsExamSongId as string]
 	if (tieList) {
-		const partIndex = query["part-index"] || '0'
+		const partIndex = state.partIndex + ""
 		const tie = tieList.find((item) => item.parts.includes(partIndex))
 		if (!tie) return
 		// 延音线和连线重叠
@@ -156,12 +156,95 @@ export const resetGivenFormate = () => {
 	
 };
 
+const initNoteCoord = () => {
+	const allNoteDot: any = Array.from(document.querySelectorAll('.node-dot'));
+ 	state.noteCoords = allNoteDot.map((note: any) => {
+		const note_bbox = note?.getBoundingClientRect?.() || { x: 0, y: 0 };
+		return {
+			x: note_bbox.x,
+			y: note_bbox.y
+		}
+	})
+	console.log(11111,state.noteCoords)
+}
+
+export const moveGracePosition = (needTrans?: boolean) => {
+	/**
+	 * TODO:曲目:摇篮曲(节奏练习)-倚音位置 特殊处理
+	 */
+	const specialIds = ['1788850864767643649','1788502467554750466','1789839575249596417','1788501975122489346','1796006876341813249'];
+	if (specialIds.includes(state.cbsExamSongId) || needTrans) {
+		const lastCurve: any = Array.from(document.getElementsByClassName('vf-curve'))?.last();
+		if (lastCurve) {
+			lastCurve.style.display = 'none';
+		}
+		if (state.musicRenderType === 'staff') {
+			// const transNoteDom = document.getElementById('vf-auto2182')?.getElementsByClassName('vf-modifiers')?.[0];
+			// const transBeamDom = document.getElementById('auto3167')?.parentNode?.getElementsByClassName('vf-beams')?.[0];
+			// if (transNoteDom) {
+			// 	transNoteDom.style.transform = 'translateX(-0.5rem)';
+			// }
+			// if (transBeamDom) {
+			// 	transBeamDom.style.transform = 'translateX(-0.5rem)';
+			// }
+		} else {
+			// vf-auto2172 , vf-auto2384
+			const signatureDom = document.getElementById('auto2670');
+			const signatureDom2 = document.getElementById('auto2710');
+			const signatureDom3 = document.getElementById('auto3099');
+			const signatureDom4 = document.getElementById('auto3339');
+
+			const needTransLateDom: any = state.cbsExamSongId == '1789839575249596417' && document.getElementById('vf-auto1554')?.getElementsByClassName('vf-modifier')?.[0];
+			const arrowDom = state.cbsExamSongId == '1789839575249596417' && document.getElementById('vf-auto1554-lines');
+
+			const needTransLateDom2: any = state.cbsExamSongId == '1788501975122489346' && document.getElementById('vf-auto2116')?.getElementsByClassName('vf-modifier')?.[0];
+			const arrowDom2 = state.cbsExamSongId == '1788501975122489346' && document.getElementById('vf-auto2116-lines');	
+			
+			const needTransLateDom3: any = state.cbsExamSongId == '1788502467554750466' && document.getElementById('vf-auto2122')?.getElementsByClassName('vf-modifier')?.[0];
+			const arrowDom3 = state.cbsExamSongId == '1788502467554750466' && document.getElementById('vf-auto2122-lines');
+
+			if (signatureDom) signatureDom.style.display = 'none';
+			if (signatureDom2) signatureDom2.style.display = 'none';
+			if (signatureDom3) signatureDom3.style.display = 'none';
+			if (signatureDom4) signatureDom4.style.display = 'none';
+
+			if (needTransLateDom) needTransLateDom.style.transform = 'translateX(-0.65rem)';
+			if (needTransLateDom2) needTransLateDom2.style.transform = 'translateX(-0.65rem)';
+			if (needTransLateDom3) needTransLateDom3.style.transform = 'translateX(-0.65rem)';
+
+			if (arrowDom) arrowDom.style.transform = 'translateX(-0.65rem)';
+			if (arrowDom2) arrowDom2.style.transform = 'translateX(-0.65rem)';
+			if (arrowDom3) arrowDom3.style.transform = 'translateX(-0.65rem)';
+
+			if (arrowDom || arrowDom2 || arrowDom3) {
+				const path: any = arrowDom ? arrowDom.querySelector('path') : arrowDom2 ? arrowDom2.querySelector('path') : arrowDom3 ? arrowDom3.querySelector('path') : null;
+				let d = path?.getAttribute("d");
+				if (d) {
+					const patchStr = d.split('L')?.last()?.split(" ")?.[0];
+					let startX = d.split("M")?.[1]?.split(" ")[0] || 0;
+					startX = startX ? Number(startX) : 0;
+					let endX = d.split("L")?.last().split(" ")[0] || 0;
+					endX = endX ? Number(endX) : 0;
+					const distanceX = endX - startX;
+					const transX = startX - distanceX;
+					d = d.replace(`L${patchStr}`,`L${transX}`);
+					path.setAttribute("d", d);
+				}
+			}
+		}
+
+	}
+}
 
 // 谱面优化
 export const resetFormate = () => {
 	container.value = document.getElementById('scrollContainer')
 	// if (state.extStyleConfigJson || !container.value) return;
 	if (!container.value) return;
+	moveGracePosition();
+	// setTimeout(() => {
+	// 	initNoteCoord();
+	// }, 0);
 	const stafflines: SVGAElement[] = Array.from((container.value as HTMLElement).querySelectorAll(".staffline"));
 	const baseStep = 4; // 两个元素相间,的间距
 	const musicalDistance = 28; // 音阶与第一条线谱的间距,默认设置为28
@@ -180,6 +263,7 @@ export const resetFormate = () => {
 		const staveSection: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-staveSection"));
 		const paths: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave path"));
 		const dotModifiers: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-stopDot"));
+		const staves: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave"));
 
 		// 获取第一个线谱的y轴坐标
 		const firstLinePathY = paths[0]?.getBBox().y || 0
@@ -449,8 +533,40 @@ export const resetFormate = () => {
 			}
 		};
 
+		// 给小节添加背景色
+		staves.forEach((stave: any) => {
+			const list = [
+				Array.from(stave?.querySelectorAll(".vf-clef") || []),
+				Array.from(stave?.querySelectorAll(".vf-keysignature") || []),
+				Array.from(stave?.getElementsByTagName("text") || []),
+				Array.from(stave?.querySelectorAll(".vf-StaveSection") || []),
+			].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);
 	}
-
+	
 	// setTimeout(() => this.resetGlobalText());
 };
 // 技巧文本
@@ -560,7 +676,7 @@ const collisionDetection = (a: SVGAElement, b: SVGAElement, distance: number = 0
 
 /** 全局曲谱配置 */
 export const setGlobalMusicSheet = () => {
-	const partIndex = query["part-index"] || '0'
+	const partIndex = state.partIndex + ""
 	/** 延音线方向问题 start */
 	const stavetieList = [
 	  {id: '12644', part_index: '25', direction: 1}
@@ -771,7 +887,7 @@ export const setGlobalMusicSheet = () => {
 	const customAccentItem = customAccentList.find(({id, part_index}) => {
 	  return id == state.cbsExamSongId && part_index == partIndex
 	})
-	if (customAccentItem) {
+	if (customAccentItem || state.isEvxml) {
 	  setGlobalData('customAccentItem', true)
 	}
 	/** 全声部声部 +  */

+ 296 - 14
src/helpers/formateMusic.ts

@@ -1,6 +1,7 @@
 import dayjs from "dayjs";
 import duration from "dayjs/plugin/duration";
 import state, { customData } from "/src/state";
+import { metronomeData as metronomeDataState } from "./metronome"
 import { browser } from "../utils/index";
 import {
 	isSpecialMark,
@@ -17,6 +18,11 @@ const browserInfo = browser();
 dayjs.extend(duration);
 
 /**
+ * 需要隐藏的中文速度文本
+ */
+const hideSpeedWords: string[] = ["中速"];
+
+/**
  * 获取节拍器的时间
  * @param speed 速度
  * @param firstMeasure 曲谱第一个小节
@@ -38,7 +44,7 @@ export const getFixTime = (speed: number) => {
 		numerator = numerator*2;
 	}
 	// console.log('diff', speed, duration, formatBeatUnit(beatUnit), denominator, numerator, (numerator / denominator))
-	return state.isOpenMetronome ? (60 / speed) * formatBeatUnit(beatUnit) * (numerator / denominator) : 0;
+	return (60 / speed) * formatBeatUnit(beatUnit) * (numerator / denominator);
 };
 
 export const retain = (time: number) => {
@@ -378,6 +384,7 @@ export const onlyVisible = (xml: string, partIndex: number): string => {
 	// console.log(visiblePartInfo, partIndex)
 	// 根据后台已选择的分轨筛选出能切换的声轨
 	state.partListNames = partListNames;
+	// console.log('分轨名称',state.partListNames)
 	if (visiblePartInfo) {
 		const id = visiblePartInfo.getAttribute("id");
 		Array.from(parts).forEach((part: any) => {
@@ -613,14 +620,22 @@ export const formatZoom = (num = 1) => {
 /** 格式化曲谱
  * 1.全休止符的小节,没有音符默认加个全休止符
  */
-export const formatXML = (xml: string): string => {
+export const formatXML = (xml: string, xmlUrl?: string): string => {
 	if (!xml) return "";
+	
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
 	const measures = Array.from(xmlParse.getElementsByTagName("measure"));
 	const repeats: any = Array.from(xmlParse.querySelectorAll('repeat'));
 	compatibleXmlPitchVoice(xmlParse);
+	// 获取作词、作曲家
+	getComposer(xmlParse);
 	// 处理重复小节信息
 	parseXmlToRepeat(repeats)
+	// 解析处理evxml
+	if (state.isEvxml) {
+		analyzeEvxml(xmlParse, xmlUrl);
+		customizationXml(xmlParse);
+	}
 	// const words: any = xmlParse.getElementsByTagName("words");
 	// for (const word of words) {
 	// 	if (word && word.textContent?.trim() === "筒音作5") {
@@ -644,6 +659,37 @@ export const formatXML = (xml: string): string => {
 		  speed = Number(measure.getElementsByTagName('per-minute')[0]?.textContent)
 		}
 		const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || "256");
+		// 如果note节点里面有space节点,并且没有duration节点,代表这是一个空白节点,需要删除
+		if (measure.getElementsByTagName("note").length && state.isEvxml) {
+			const noteList = Array.from(measure.getElementsByTagName("note")) || [];
+			noteList.forEach((note: any) => {
+				// if (note.getElementsByTagName("space").length && !note.getElementsByTagName("duration").length) {
+				// 	measure.removeChild(note);
+				// }
+				// 非倚音音符
+				if (!note.getElementsByTagName("grace").length) {
+					if (!note.getElementsByTagName("duration").length || (note.getElementsByTagName("duration").length && note.getElementsByTagName("duration")[0]?.textContent == 0)) {
+						measure.removeChild(note);
+					}
+				}
+			});
+		}
+		// 如果有特殊中文速度文本,需要删除
+		const reg = new RegExp("[\\u4E00-\\u9FFF]+", "g");
+		if (measure.getElementsByTagName("words").length && state.isEvxml) {
+			const wordList = Array.from(measure.getElementsByTagName("words")) || [];
+			wordList.forEach((word: any) => {
+				// TODO:删除妙极客曲子无意义的words
+				// wordArr?.push(word?.textContent)
+				if (word?.textContent && reg.test(word?.textContent) && word?.parentNode?.parentNode) {
+					measure.removeChild(word.parentNode.parentNode);
+					// deleteWordArr?.push(word?.textContent)
+				}
+				// if(hideSpeedWords.includes(word?.textContent) && word?.parentNode?.parentNode) {
+				// 	measure.removeChild(word.parentNode.parentNode);
+				// }
+			})
+		}
 		if (measure.getElementsByTagName("note").length === 0) {
 			const forwardTimeElement = measure.getElementsByTagName("forward")[0]?.getElementsByTagName("duration")[0];
 			if (forwardTimeElement) {
@@ -661,6 +707,9 @@ export const formatXML = (xml: string): string => {
 		}
 	}
 	// 如果曲谱详情接口没有返回速度,则取xml第一小节的速度,如果取不到,则取默认速度:100
+	if (!speed || speed == -1) {
+		speed = 100
+	}
 	if (!state.originSpeed) {
 		state.originSpeed = state.speed = speed || 100
 	}
@@ -726,6 +775,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	let staveNoteIndex = 0;
 	let staveIndex = 0;
 
+	let preNoteEndTime = 0; // 上一个音符的结束时间
+
 	const _notes = [] as any[];
 	if (state.gradualTimes) {
 		console.log("后台设置的渐慢小节时间", state.gradual, state.gradualTimes);
@@ -735,6 +786,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	const currentTimes = [] as any[];
 	let isSetNextNoteReal = false;
 	let differFrom = 0;
+	// let testIdx = 0;
+	let repeatIdx = 0; // 循环的次数
 	while (!iterator.EndReached) {
 		// console.log({ ...iterator });
 		const voiceEntries = iterator.CurrentVoiceEntries?.[0] ? [iterator.CurrentVoiceEntries?.[0]] : [];
@@ -818,10 +871,19 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 		}
 
 		iterator.moveToNextVisibleVoiceEntry(false);
+		// 从头开始循环,repeatIdx标记+1
+		if (iterator.backJumpOccurred) {
+			repeatIdx += 1;
+		}
+		iterator.repeatIdx = repeatIdx;
+		// console.log('小节',testIdx,iterator.repeatIdx,iterator.EndReached,iterator.currentMeasureIndex,iterator.backJumpOccurred,iterator.forwardJumpOccurred)
+		// testIdx += 1;
 	}
 	// 是否是变速的曲子
 	const hasVaryingSpeed = _notes.some((item: any) => item.measuresTempoInBPM !== _notes[0].measuresTempoInBPM)
-	console.log('变速曲子',hasVaryingSpeed)
+	console.log('变速曲子',hasVaryingSpeed, _notes)
+	let noteIds: any = [];
+	// let voicesBBox: any = null;
 	for (let { note, iterator, currentTime, isDouble, isMutileSubject } of _notes) {
 		if (note) {
 			if (preMeasureNumber != note?.sourceMeasure?.MeasureNumberXML) {
@@ -852,7 +914,28 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			if (metronomeNoteIndex !== 0 && metronomeNoteIndex > si) {
 				measureSpeed = allNotes[allNotes.length - 1]?.speed || 100;
 			}
-			const activeVerticalMeasureList = [note.sourceMeasure.verticalMeasureList?.[0]] || [];
+			// 当前的分轨
+			let activeVerticalMeasureList: any = [];
+			/**
+			 * bug: #9959
+			 * 多分轨合并展示,第一分轨又可能获取不到对应的音符,需要在当前小节中音符最多的分轨中去查找音符
+			 */
+			// if (state.isCombineRender) {
+			// 	const allTrackList = note.sourceMeasure.verticalMeasureList;
+			// 	let maxIdx = 0, maxNote = 0;
+			// 	allTrackList.forEach((item: any, index: number) => {
+			// 		if (item?.vfVoices['1']?.tickables?.length > maxNote) {
+			// 			maxIdx = index
+			// 			maxNote = item?.vfVoices['1']?.tickables?.length
+			// 		}
+			// 	})
+			// 	activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[maxIdx]] || [];
+			// } else {
+			// 	activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[0]] || [];
+			// }
+			
+			activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[0]] || [];
+
 			const { realValue } = iterator.currentTimeStamp;
 			const { RealValue: vRealValue, Denominator: vDenominator } = formatDuration(
 				iterator.currentMeasure.activeTimeSignature,
@@ -873,12 +956,18 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// 管乐迷,按自定义按读取到的音符时值
 			if (customNoteCurrentTime) {
 				if (isMutileSubject && currentTimes[i + 1] > 0 && NoteRealValue > currentTimes[i + 1]) {
-					console.log(NoteRealValue, currentTimes[i + 1])
+					// console.log(NoteRealValue, currentTimes[i + 1])
 					NoteRealValue = currentTimes[i + 1];
 				}
 			}
 
 			let relativeTime = usetime;
+
+			// 妙极客的曲子,修复有的音符有times,有的音符没有times导致的,累计时长错误问题
+			if (state.isEvxml && relativeTime < preNoteEndTime - fixtime) {
+				relativeTime = preNoteEndTime - fixtime
+			}
+
 			let beatSpeed = 0;
 			// 速度不能为0 此处的速度应该是按照设置的速度而不是校准后的速度,否则mp3速度不对
 			if (measureSpeed !== baseSpeed && !hasVaryingSpeed) {
@@ -889,8 +978,13 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// let beatSpeed = measureSpeed || baseSpeed
 			// 如果有节拍器,需要将节拍器的时间算出来
 			if (i === 0) {
-				fixtime += getFixTime(beatSpeed);
-				state.fixtime = fixtime;
+				if(state.isOpenMetronome){
+					fixtime += getFixTime(beatSpeed);
+					state.fixtime = fixtime;
+				}
+				// 存储mp3节拍器时间
+				metronomeDataState.xmlMp3BeatFixTime = getFixTime(beatSpeed)
+				// 
 				// console.log("fixtime:", fixtime, '速度:', beatSpeed, "state.isSpecialBookCategory:", state.isSpecialBookCategory, 'state.isOpenMetronome:', state.isOpenMetronome);
 			}
 			// console.log(getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator))
@@ -969,6 +1063,20 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			const measureLength = vRealValue * vDenominator * (60 / beatSpeed);
 			// console.table({value: iterator.currentTimeStamp.realValue, vRealValue,NoteRealValue, noteLength,measureLength, MeasureNumberXML: note.sourceMeasure.MeasureNumberXML})
 			// console.log(i, Math.min(vRealValue, NoteRealValue),noteLength,gradualLength, formatBeatUnit(beatUnit),beatSpeed, NoteRealValue * formatBeatUnit(beatUnit) * (60 / beatSpeed) )
+			/**
+			 * TODO:摇篮曲-人音-排箫(1788501975122489346),第12小节音符持续时间特殊处理
+			*/
+			if (['1788501975122489346','1788502467554750466'].includes(state.cbsExamSongId)) {
+				if (i == 13) {
+					noteLength = noteLength / 2;
+				}
+				if (i == 44) {
+					noteLength = noteLength * 6;
+				}
+				if (i == 56) {
+					noteLength = noteLength * 4;
+				}
+			}
 			usetime += noteLength;
 			relaMeasureLength += noteLength;
 			let relaEndtime = noteLength + relativeTime;
@@ -976,6 +1084,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			const fixedKey = note.fixedKey || 0;
 			// const svgElement = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables[si];
 			const svgElement = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[staveNoteIndex];
+
 			// console.log('si',si,i)
 			// console.log(note.sourceMeasure.MeasureNumberXML,note,svgElement, NoteRealValue, measureLength)
 			if (allNotes.length && allNotes[allNotes.length - 1].relativeTime === relativeTime) {
@@ -1006,6 +1115,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				} else {
 					if (difftime > 0) {
 						fixtime += difftime;
+						state.fixtime = fixtime;
 					}
 				}
 				// 管乐迷 diff获取不准确时, 弱起补齐
@@ -1013,6 +1123,13 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 					// difftime = iterator.currentTimeStamp.realValue * formatBeatUnit(beatUnit) * (60 / beatSpeed);
 					// fixtime += difftime;
 				}
+
+				// 如果是evxml,fixtime取读取xml的值
+				if (state.isEvxml) {
+					fixtime = state.evXmlBeginTime ? state.evXmlBeginTime : fixtime
+					state.fixtime = fixtime
+				}
+				console.log('节拍器时间',fixtime,state.evXmlBeginTime)
 			}
 			let stave = activeVerticalMeasureList[0]?.stave;
 
@@ -1030,6 +1147,22 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// console.log(note.tie)
 			// console.log('👀看看endtime', duration, relaEndtime, fixtime, i)
 			// console.log('频率',note?.pitch?.frequency,i)
+			/**
+			 * evxml的曲子,如果曲谱xml中带有times信息,则音符时值优先取times中的值
+			 */
+			let evNoteStartTime = 0, evNoteEndTime = 0;
+			if (state.isEvxml && note?.noteTimeInfo?.length) {
+				const idx = noteIds.filter((item: any) => item === svgElement?.attrs.id)?.length || 0
+				evNoteStartTime = note?.noteTimeInfo[idx]?.begin
+				evNoteEndTime = note?.noteTimeInfo[idx]?.end
+				if (evNoteStartTime) {
+					relativeTime = evNoteStartTime - fixtime
+					// usetime = evNoteStartTime - fixtime
+				}
+				// usetime = evNoteStartTime - fixtime
+			}
+			svgElement?.attrs.id && noteIds.push(svgElement?.attrs.id)
+
 			const nodeDetail = {
 				isStaccato: note.voiceEntry.isStaccato(),
 				isRestFlag: note.isRestFlag,
@@ -1057,8 +1190,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				note: note.halfTone + 12, // see issue #224
 				fixtime, // 弱起补充的时间
 				relativeTime: retain(relativeTime),
-				time: retain(relativeTime + fixtime), // 开始播放的时间
-				endtime: retain(relaEndtime + fixtime), // 播放完成的时间
+				time: state.isEvxml && evNoteStartTime ? retain(evNoteStartTime) : retain(relativeTime + fixtime), // 开始播放的时间
+				endtime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + fixtime), // 播放完成的时间
 				relaEndtime: retain(relaEndtime),
 				realValue,
 				halfTone: note.halfTone,
@@ -1072,10 +1205,26 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				noteLength: 1,
 				osdmContext: osmd,
 				speedbeatUnit: beatUnit,
-				multipleRestMeasures: multipleRestMeasures,
+				multipleRestMeasures: multipleRestMeasures, // 当前合并小节的索引,从1开始到当前的totalMultipleRestMeasures结束,
+				totalMultipleRestMeasures, // 当前小节总的合并小节数
 				measureSpeed,  // 小节速度
 				maxNoteNum: note.maxNoteNum, // 当前小节音符最多的分轨的音符数量
+				repeatIdx: iterator.repeatIdx || 0, // 标记是第几遍循环,从0开始
 			};
+			// 如果是妙极客的曲子,并且第二遍循环播放需要等待时间,并且是第二遍循环的第一个小节的第一个音符
+			// if (state.isEvxml && state.secondEvXmlBeginTime && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
+			// 	nodeDetail.time = nodeDetail.time + state.secondEvXmlBeginTime;
+			// 	nodeDetail.endtime = nodeDetail.endtime + state.secondEvXmlBeginTime;
+			// 	usetime = usetime + state.secondEvXmlBeginTime;
+			// 	relativeTime = relativeTime + state.secondEvXmlBeginTime;
+			// }
+			if (state.isEvxml && nodeDetail.repeatIdx && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
+				const currentWaitTime = state.evXmlBeginArr[nodeDetail.repeatIdx] || 0;
+				nodeDetail.time = nodeDetail.time + currentWaitTime;
+				nodeDetail.endtime = nodeDetail.endtime + currentWaitTime;
+				usetime = usetime + currentWaitTime;
+				relativeTime = relativeTime + currentWaitTime;
+			}			
 			nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail);
 			nodeDetail.duration = nodeDetail.endtime - nodeDetail.time;
 			let tickables = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables || [];
@@ -1084,6 +1233,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}
 			// console.log(note.sourceMeasure.MeasureNumberXML, note.sourceMeasure.verticalSourceStaffEntryContainers.length)
 			// console.log('👀看看endtime', nodeDetail.duration, relaEndtime, fixtime, i)
+			// console.log('音符时间',nodeDetail.i,nodeDetail.time,nodeDetail.endtime)
 			tickables = tickables.filter((tickable: any) => tickable.attrs?.type !== "GhostNote")
 			const maxNum = (state.isCombineRender && note.maxNoteNum) ? note.maxNoteNum : tickables.length;
 			nodeDetail.noteLength = maxNum || 1;
@@ -1101,11 +1251,14 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				relaMeasureLength = 0;
 				measures = [];
 			}
+			preNoteEndTime = nodeDetail.endtime;
 		}
 		i++;
 	}
 	// 按照时间轴排序
 	const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => ({ ...item, i: index }));
+	// const sortArray = allNotes.sort((a, b) => a.time - b.time).map((item, index) => ({ ...item, i: index }));
+	// const sortArray = allNotes.map((item, index) => ({ ...item, i: index }));
 	console.timeEnd("音符跑完时间");
 	try {
 		osmd.cursor.reset();
@@ -1135,6 +1288,18 @@ export const getNoteByMeasuresSlursStart = (note: any) => {
 	return activeNote;
 };
 
+// 解析xml,获取作词、作曲家名称
+const getComposer = (xmlParse: any) => {
+	const creators: any = Array.from(xmlParse.querySelectorAll('creator'));
+	for (const creator of creators) {
+		if (creator && creator.getAttribute('type') === 'composer' && !state.musicComposer) {
+			state.musicComposer = creator.textContent?.trim() || '';
+		}
+		if (creator && creator.getAttribute('type') === 'lyricist' && !state.musicLyricist) {
+			state.musicLyricist = creator.textContent?.trim() || '';
+		}
+	}
+}
 
 // 通过xml信息获取重播的小节信息
 const parseXmlToRepeat = (repeats: any) => {
@@ -1162,7 +1327,7 @@ const parseXmlToRepeat = (repeats: any) => {
 		}
 	}
 	state.repeatInfo = repeatInfo
-	console.log('重播',repeatInfo)
+	// console.log('重播',repeatInfo)
 }
 
 // 校验当前选段是否满足重播条件
@@ -1192,6 +1357,120 @@ export const verifyCanRepeat = (startNum: number, endNum: number) => {
 	}
 }
 
+// 处理妙极客xml谱面
+const customizationXml = (xmlParse: any) => {
+	const credits: any = Array.from(xmlParse.querySelectorAll('credit'));
+	const creators: any = Array.from(xmlParse.querySelectorAll('creator'));
+	const graces: any = Array.from(xmlParse.querySelectorAll('grace'));
+	const measures: any[] = Array.from(xmlParse.getElementsByTagName("measure"));
+	const notes: any[] = Array.from(xmlParse.getElementsByTagName("note"));
+
+	// 获取音符最多的歌词数,用于自定义循环播放次数
+	let maxLyricNum = 0;
+	if (notes && notes.length) {
+		for (const note of notes) {
+			if (maxLyricNum < note.getElementsByTagName("lyric").length) {
+				maxLyricNum = note.getElementsByTagName("lyric").length
+			}
+		}
+	}
+	state.maxLyricNum = maxLyricNum;
+	// state.osmd.EngravingRules.DYCustomRepeatCount = maxLyricNum;
+	;(window as any).DYCustomRepeatCount = state.maxLyricNum;
+	console.log('歌词次数',maxLyricNum)
+
+	if (credits && credits.length) {
+		for (const credit of credits) {
+			if (credit.getElementsByTagName("credit-type")?.[0]?.textContent === 'lyricist') {
+				const creditWord = credit.getElementsByTagName("credit-words")
+				creditWord?.[0].setAttribute('justify', 'right')
+			}
+		}
+	}
+	if (creators && creators.length) {
+		for (const creator of creators) {
+			if (creator.getAttribute('type') === 'lyricist') {
+				// creator.textContent = '测试一下1';
+			}
+			
+		}
+	}
+	// 妙极客xml的倚音(grace)标签需要加上slash=yes属性
+	if (graces && graces.length) {
+		for (const grace of graces) {
+			grace?.setAttribute('slash','yes');
+			// console.log(grace,'倚音')
+		}
+	}
+	// 妙极客xml部分小节没有音符,只有Segno,该小节不需要渲染,表示的是反复标记
+	for (const measure of measures) {
+		const hasNote = measure.getElementsByTagName("note").length;
+		const hasSegno = measure.getElementsByTagName("segno").length;
+		const sounds = Array.from(measure.getElementsByTagName("sound"));
+		const hasSoundSegno = sounds.some((item: any) => item.getAttribute('segno') === 'segno' );
+		if (!hasNote && hasSegno && hasSoundSegno) {
+			const parent = measure.parentNode;
+			parent.removeChild(measure);
+		}
+	}
+
+	/**
+	 * bug: #10289,曲目:1782672015612725196、1788040971888537602
+	 * 妙极客xml,多遍歌词循环的曲目,如果没有repeat标签,需要加上repeat标签
+	 * */
+	if (maxLyricNum > 1) {
+		const hasRepeat = xmlParse.querySelectorAll('repeat').length > 0
+		if (!hasRepeat) {
+			const lastMeasure = measures.last();
+			if (lastMeasure.getElementsByTagName('barline').length) {
+				const barlineDom = lastMeasure.getElementsByTagName('barline')[0]
+				barlineDom.innerHTML = barlineDom.innerHTML + `<repeat direction="backward" />`;
+			} else {
+				lastMeasure.innerHTML = lastMeasure.innerHTML + `
+				<barline location="right">
+					<bar-style>light-heavy</bar-style>
+					<repeat direction="backward" />
+				</barline>`
+			}
+			// console.log(lastMeasure)
+		}
+	}
+}
+
+// 计算evxml的起始播放时间
+const analyzeEvxml = (xmlParse: any, xmlUrl?: string) => {
+	// xml拍号数
+	const xmlNum = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[0]?.getAttribute('num');
+	const denNum = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[0]?.getAttribute('den');
+	const xmlNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('num');
+	const denNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('den');
+	const timeGaps: any = xmlParse.getElementsByTagName("timegap")?.length ? Array.from(xmlParse.getElementsByTagName("timegap")?.[0]?.getElementsByTagName("values")?.[0]?.getElementsByTagName("item")) : [];
+	// 第一个音符的起始时间
+	const firstMeasure = xmlParse.getElementsByTagName("measure")[0];
+	if (firstMeasure) {
+		const firstNoteBeginTime = firstMeasure.getElementsByTagName("times")[0]?.getElementsByTagName("time")[0]?.getAttribute('begin');
+		state.evXmlBeginTime = firstNoteBeginTime ? firstNoteBeginTime / 1000 : xmlNum ? 60 / state.originSpeed * xmlNum * 4/denNum : 0;
+		state.secondEvXmlBeginTime = firstNoteBeginTime ? 0 : xmlNum2 ? 60 / state.originSpeed * xmlNum2 * 4/denNum2 : 0;
+		const hasTimeGap = xmlParse.getElementsByTagName("timegap").length > 0;
+		const hasTimes = xmlParse.getElementsByTagName("times").length > 0;
+
+		if (timeGaps && timeGaps.length && !firstNoteBeginTime) {
+			for (const timeGap of timeGaps) {
+				const num: any = timeGap?.getAttribute('num'), den: any = timeGap?.getAttribute('den');
+				const startTime = num ? 60 / state.originSpeed * num * 4/den : 0;
+				state.evXmlBeginArr.push(startTime)
+			}
+		}
+
+		console.log('🚀 ~ evxml解析','有timegap:',hasTimeGap,'有times:',hasTimes,'timegap集合',state.evXmlBeginArr,'第一个timegap',state.evXmlBeginTime)
+	}
+
+	// if (!hasTimeGap && !hasTimes) {
+	// 	state.noTimes.push(xmlUrl)
+	// }
+	
+}
+
 /**
  * 兼容处理xml声部移调
  * 打谱软件可能会自动处理移调,这类型的xml就不用通过程序移调了
@@ -1201,12 +1480,12 @@ export const verifyCanRepeat = (startNum: number, endNum: number) => {
  */
 export const compatibleXmlPitchVoice = (xmlParse: any) => {
 	const partNames = Array.from(xmlParse.getElementsByTagName('part-name'));
-	const partListNames = partNames.map((item: any) => item[0]?.textContent?.trim().toLocaleUpperCase !== "COMMON");
+	const partListNames = partNames.filter((item: any) => item?.textContent?.trim().toLocaleUpperCase() !== "COMMON");
 	if (partListNames.length == 1) {
 		const instrumentNames = Array.from(xmlParse.getElementsByTagName('instrument-name')) || [];
 		// @ts-ignore
 		const instrumentName = instrumentNames[0]?.textContent?.trim()?.toLocaleLowerCase() || ''
-		// console.log('ins名称',instrumentName)
+		// console.log('instrument名称',instrumentName)
 		// 是否需要程序处理移调
 		let xmlNeedAdjustVoice = false;
 		switch (state.musicalCodeId) {
@@ -1222,7 +1501,10 @@ export const compatibleXmlPitchVoice = (xmlParse: any) => {
 				break;
 			case 35:
 				xmlNeedAdjustVoice = !instrumentName || instrumentName.includes('solo') || instrumentName.includes('woodwind') ? true : false
-				break;				
+				break;		
+			case 39:
+				xmlNeedAdjustVoice = !instrumentName || instrumentName.includes('solo') || instrumentName.includes('whistling') ? true : false
+				break;						
 			default:
 				xmlNeedAdjustVoice = !instrumentName || instrumentName.includes('solo') ? true : false
 				break;

+ 25 - 3
src/helpers/metronome.ts

@@ -9,8 +9,9 @@ import { browser } from "/src/utils/index";
 import state from "/src/state";
 import { Howl } from "howler";
 import tockAndTick from "/src/constant/tockAndTick.json";
-import tickWav from "/src/assets/tick.wav";
-import tockWav from "/src/assets/tock.wav";
+import tickWav from "/src/assets/tick.mp3";
+import tockWav from "/src/assets/tock.mp3";
+import { audioData as audioDataState } from "../view/audio-list"
 
 type IOptions = {
 	speed: number;
@@ -41,6 +42,7 @@ export const metronomeData = reactive({
 	cursorTips: '' as string, // 光标模式提示文字
 	followAudioIndex: 1, // 当前的拍数
 	totalNumerator: 2, // 总拍数
+	xmlMp3BeatFixTime: 0   // 当前xml mp3节拍器的时间 切换演奏和演唱计算时间用
 });
 
 watch(
@@ -110,6 +112,7 @@ class Metronome {
 	init(times: any[]) {
 		this.calculation(times);
 		metronomeData.activeList = [];
+		this.initPlayer()
 	}
 	initPlayer() {
 		// if (!this.source1) {
@@ -119,7 +122,7 @@ class Metronome {
 		// 	this.source2 = this.loadAudio2();
 		// }
 		// metronomeData.initPlayerState = true;
-
+		if(metronomeData.initPlayerState) return
 		Promise.all([this.createAudio(tickWav), this.createAudio(tockWav)]).then(
 			([tick, tock]) => {
 				if (tick) {
@@ -179,6 +182,25 @@ class Metronome {
 	};
 	// 播放
 	playAudio = () => {
+		/* 如果是 评测模式且不为MIDI并且节拍器资源加载成功的时候  不运行节拍器播放*/
+		if (state.modeType === "practise" && state.playMode !== "MIDI") {
+			if(state.playType === "play" && state.playSource === "music" && audioDataState.songCollection.beatSongEle){
+				return
+			}			
+			if(state.playType === "play" && state.playSource === "background" && audioDataState.songCollection.betaBackgroundEle){
+				return
+			}			
+			if(state.playType === "sing" && state.playSource === "music" && audioDataState.songCollection.betaFanSongEle){
+				return
+			}			
+			if(state.playType === "sing" && state.playSource === "background" && audioDataState.songCollection.betaBanSongEle){
+				return
+			}			
+			if(state.playType === "sing" && state.playSource === "mingSong" && audioDataState.songCollection.betaMingSongEle){
+				return
+			}			
+		}
+		console.log("播放自带的节拍器 233333")
 		if (!metronomeData.initPlayerState || state.playState === 'paused') return;
 		const beatVolume = state.setting.beatVolume / 100
 		// this.source = metronomeData.activeMetro?.index === 0 ? this.source1 : this.source2;

+ 68 - 0
src/helpers/oss-file-upload.ts

@@ -0,0 +1,68 @@
+import request from "../utils/request"
+import COS from "cos-js-sdk-v5"
+
+const tencentBucket = "daya-online-1303457149"
+const ossType = "tencent"
+
+export async function fileUpload(fileName:string,file:Blob) {
+   const { data } = await getUploadSign(fileName)
+   return await onOnlyFileUpload(data.signature, {
+      fileName,
+      file
+   })
+}
+
+const getUploadSign = async (fileName:string) => {
+   const fileUrl = "yjl/" + fileName
+   return request.post("/open/getUploadSign", {
+      data: {
+         postData: {
+            key: fileUrl
+         },
+         pluginName: ossType,
+         bucketName: tencentBucket,
+         filename: fileUrl
+      },
+      requestType: "json",
+      params: { pluginName: ossType }
+   })
+}
+
+const onOnlyFileUpload = async (signature: string, params: { fileName: string; file: Blob }) => {
+   let file = ""
+   let errorObj: any = null
+   const cos = new COS({
+      Domain: "https://oss.dayaedu.com",
+      Protocol: "https",
+      getAuthorization: async (options, callback: any) => {
+         callback({ Authorization: signature })
+      }
+   })
+   await cos
+      .uploadFile({
+         Bucket: tencentBucket /* 填写自己的 bucket,必须字段 */,
+         Region: "ap-nanjing" /* 存储桶所在地域,必须字段 */,
+         Key: `yjl/${params.fileName}`,
+         /* 存储在桶里的对象键(例如:1.jpg,a/b/test.txt,图片.jpg)支持中文,必须字段 */
+         Body: params.file, // 上传文件对象
+         SliceSize: 1024 * 1024 * 500 /* 触发分块上传的阈值,超过5MB使用分块上传,小于5MB使用简单上传。可自行设置,非必须 */,
+         onProgress: function (progressData) {
+            // onProgress({ percent: Math.ceil((progressData.percent || 0) * 100) })
+         }
+      })
+      .then((res: any) => {
+         if (res.Location?.indexOf("http") >= 0) {
+            file = res.Location
+         } else {
+            file = "https://" + res.Location
+         }
+      })
+      .catch((error: any) => {
+         errorObj = error
+      })
+   if (file) {
+      return file
+   } else {
+      throw new Error(errorObj)
+   }
+}

+ 59 - 0
src/hooks/useDrag/dragbom.tsx

@@ -0,0 +1,59 @@
+import { defineComponent, computed, reactive, onMounted } from 'vue';
+import styles from './index.module.less';
+// 底部拖动区域
+export default defineComponent({
+  name: 'dragBom',
+  emits: ["guideDone"],
+	props: {
+		/** 是否显示引导 */
+		showGuide: {
+			type: Boolean,
+			default: false,
+		},
+	},
+  setup(props, { emit }) {
+    const data = reactive({
+      guidePos: "bottom" as "bottom" | "left" | "right",
+    });
+
+    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 dragTop = dragBBox?.top || 0;
+      const dragLeft = dragBBox?.left || 0;
+      // 引导页出现在下边
+      if (pageHeight - dragTop > guideHeight) {
+        data.guidePos = "bottom"
+      } else {
+        // 引导页出现在左边or右边
+        data.guidePos = dragLeft > guideWidth ? "left" : "right"
+      }
+    }
+    onMounted(() => {
+      setTimeout(() => {
+        initGuidePos();
+      }, 0);
+    });
+    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>
+      </div>
+        {
+          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>          
+        }
+
+      </>
+    );
+  }
+});

BIN
src/hooks/useDrag/img/left.png


BIN
src/hooks/useDrag/img/modalDragBg.png


BIN
src/hooks/useDrag/img/modalDragBg2.png


BIN
src/hooks/useDrag/img/modalDragBgLeft.png


BIN
src/hooks/useDrag/img/modalDragBgRight.png


BIN
src/hooks/useDrag/img/modalDragDone.png


BIN
src/hooks/useDrag/img/right.png


+ 88 - 0
src/hooks/useDrag/index.module.less

@@ -0,0 +1,88 @@
+.dragbomBox{
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  height: 36px;
+  width: 100%;
+  padding: 0 14px 0 15px;
+}
+.dragBom {
+  width: 100%;
+  height: 21px;
+  display: flex;
+  justify-content: space-between;
+  border-radius: 0 0 7px 7px;
+  overflow: hidden;
+  .box {
+    width: 21px;
+    height: 100%;
+    background: url('./img/left.png') no-repeat;
+    background-size: 100% 100%;
+    &.right {
+      background: url('./img/right.png') no-repeat;
+      background-size: 100% 100%;
+    }
+  }
+}
+.guide {
+  position: absolute;
+  left: 0;
+  top: calc(100% - 10px);
+  z-index: 3000;
+  &::before {
+    content: "";
+    display: block;
+    position: fixed;
+    left: -100vw;
+    top: -100vh;
+    z-index: 9;
+    width: 200vw;
+    height: 200vh;
+    background: rgba(0,0,0,0.2);
+  }
+  .guideBg {
+    position: relative;
+    z-index: 99;
+    width: 200px;
+    height: 102px;
+    background: url('./img/modalDragBg.png') no-repeat;
+    background-size: 100% 100%;
+  }
+  .guideDone {
+    position: absolute;
+    z-index: 99;
+    left: 34.6%;
+    top: 72.2%;
+    width: 50px;
+    height: 20px;
+    background: url('./img/modalDragDone.png') no-repeat;
+    background-size: 100% 100%;
+    cursor: pointer;
+  }
+  &.guideTop {
+    top: initial;
+    bottom: 2px;
+    .guideBg {
+      background: url('./img/modalDragBg2.png') no-repeat;
+      background-size: 100% 100%;
+    }
+  }
+  &.guideLeft {
+    top: initial;
+    left: -186px;
+    bottom: -4px;
+    .guideBg {
+      background: url('./img/modalDragBgLeft.png') no-repeat;
+      background-size: 100% 100%;
+    }
+  }
+  &.guideRight {
+    top: initial;
+    left: calc(100% - 12px);
+    bottom: -4px;
+    .guideBg {
+      background: url('./img/modalDragBgRight.png') no-repeat;
+      background-size: 100% 100%;
+    }
+  }
+}

+ 163 - 0
src/hooks/useDrag/index.ts

@@ -0,0 +1,163 @@
+// 弹窗拖动
+import { ref, Ref, watch, nextTick, computed } from 'vue';
+
+type posType = {
+  top: number;
+  left: number;
+};
+
+/**
+ * @params classList  可拖动地方的class值,也为唯一值
+ * @params boxClass  容器class值必须为唯一值,这个class和useid拼接 作为缓存主键
+ * @params dragShow  弹窗是否显示
+ * @params userId    当前用户id
+ */
+export default function useDrag(
+  classList: string[],
+  boxClass: string,
+  dragShow: Ref<boolean>,
+  userId: string
+) {
+  const pos = ref<posType>({
+    top: -1, // -1 为初始值 代表没有缓存 默认居中
+    left: -1
+  });
+  const useIdDargClass = userId + boxClass;
+  watch(dragShow, () => {
+    if (dragShow.value) {
+      // 初始化pos值
+      initPos();
+      window.addEventListener('resize', refreshPos);
+      nextTick(() => {
+        const boxClassDom = document.querySelector(
+          `.${boxClass}`
+        ) as HTMLElement;
+        if (!boxClassDom) {
+          return;
+        }
+        classList.map((className: string) => {
+          const classDom = document.querySelector(
+            `.${className}`
+          ) as HTMLElement;
+          if (classDom) {
+            classDom.style.cursor = 'move';
+            drag(classDom, boxClassDom, pos);
+          }
+        });
+      });
+    } else {
+      window.removeEventListener('resize', refreshPos);
+      setCachePos(useIdDargClass, pos.value);
+    }
+  });
+  const styleDrag = computed(() => {
+    // 没有设置拖动的时候保持原本的
+    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'
+        };
+  });
+  function initPos() {
+    const posCache = getCachePos(useIdDargClass);
+    // 有缓存 用缓存的值,没有缓存用默认
+    if (posCache) {
+      pos.value = posCache;
+      nextTick(() => {
+        refreshPos();
+      });
+    }
+  }
+  function refreshPos() {
+    if(pos.value.left === -1 && pos.value.top === -1){
+      return
+    }
+    const boxClassDom = document.querySelector(`.${boxClass}`) as HTMLElement;
+    if (!boxClassDom) return;
+    const parentElementRect = boxClassDom.getBoundingClientRect();
+    const clientWidth = document.documentElement.clientWidth;
+    const clientHeight = document.documentElement.clientHeight;
+    const { top, left } = pos.value;
+    const maxLeft = clientWidth - parentElementRect.width;
+    const maxTop = clientHeight - parentElementRect.height;
+    let moveX = left;
+    let moveY = top;
+    const minLeft = 0;
+    const minTop = 0;
+    moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
+    moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
+    pos.value = {
+      top: moveY,
+      left: moveX
+    };
+  }
+  return {
+    pos,
+    styleDrag
+  };
+}
+
+// 拖动
+function drag(el: HTMLElement, parentElement: HTMLElement, pos: Ref<posType>) {
+  function mousedown(e: MouseEvent) {
+    const parentElementRect = parentElement.getBoundingClientRect();
+    const downX = e.clientX;
+    const downY = e.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);
+      moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
+      moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
+      pos.value = {
+        top: moveY,
+        left: moveX
+      };
+    }
+    function onMouseup() {
+      document.removeEventListener('mousemove', onMousemove);
+      document.removeEventListener('mouseup', onMouseup);
+    }
+    document.addEventListener('mousemove', onMousemove);
+    document.addEventListener('mouseup', onMouseup);
+  }
+  el.addEventListener('mousedown', mousedown);
+}
+
+// 缓存
+const localStorageName = 'dragCachePos';
+function getCachePos(useIdDargClass: string): null | undefined | posType {
+  const localCachePos = localStorage.getItem(localStorageName);
+  if (localCachePos) {
+    try {
+      return JSON.parse(localCachePos)[useIdDargClass];
+    } catch {
+      return null;
+    }
+  }
+  return null;
+}
+function setCachePos(useIdDargClass: string, pos: posType) {
+  const localCachePos = localStorage.getItem(localStorageName);
+  let cachePosObj: Record<string, any> = {};
+  if (localCachePos) {
+    try {
+      cachePosObj = JSON.parse(localCachePos);
+    } catch {
+      //
+    }
+  }
+  cachePosObj[useIdDargClass] = pos;
+  localStorage.setItem(localStorageName, JSON.stringify(cachePosObj));
+}

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

@@ -0,0 +1,39 @@
+import { getGuidance, setGuidance } from "../../page-instrument/custom-plugins/guide-page/api"
+import { ref } from "vue"
+
+export default function useDragGuidance() {
+   // 引导页
+   const guidanceShow = ref(false)
+   let guideInfoData: Record<string, any> = {}
+   getGuidanceShow()
+   async function getGuidanceShow() {
+      try {
+         const res = await getGuidance({ guideTag: "guideInfo" })
+         if (res.code === 200) {
+            if (res.data) {
+               const guideInfo = JSON.parse(res.data?.guideValue) || null
+               if (guideInfo) {
+                  guideInfoData = guideInfo
+                  guidanceShow.value = !guideInfo.teacherDrag
+               }
+            } else {
+               guidanceShow.value = true
+            }
+         }
+      } catch (e) {
+         console.log(e)
+      }
+   }
+   function setGuidanceShow() {
+      try {
+         setGuidance({ guideTag: "guideInfo", guideValue: JSON.stringify(Object.assign(guideInfoData, { teacherDrag: true })) })
+         guidanceShow.value = false
+      } catch (e) {
+         console.log(e)
+      }
+   }
+   return {
+      guidanceShow,
+      setGuidanceShow
+   }
+}

+ 14 - 13
src/page-colexiu/detail/index.tsx

@@ -7,7 +7,7 @@ import state, { isRhythmicExercises } from "../../state";
 import { storeData } from "../../store";
 import { setGlobalData } from "../../utils";
 import AudioList from "../../view/audio-list";
-import MusicScore, { resetMusicScore } from "../../view/music-score";
+import MusicScore from "../../view/music-score";
 import { sysMusicScoreAccompanimentQueryPage } from "../api";
 import EvaluatModel from "../evaluat-model";
 import HeaderTop from "../header-top";
@@ -98,6 +98,7 @@ export default defineComponent({
 			state.midiUrl = data.midiUrl;
 			state.parentCategoriesId = data.musicTag;
 			state.playMode = data.audioType === "MP3" ? "MP3" : "MIDI";
+			// state.originSpeed = state.speed = parseFloat(data.playSpeed) || 0;
 			state.originSpeed = state.speed = data.playSpeed;
 			state.track = data.track;
 			state.enableNotation = data.notation ? true : false;
@@ -185,16 +186,16 @@ export default defineComponent({
 		});
 
 		// 监听指法显示
-		watch(
-			() => state.setting.displayFingering,
-			() => {
-				if (state.fingeringInfo.direction === "vertical") {
-					nextTick(() => {
-						resetMusicScore();
-					});
-				}
-			}
-		);
+		// watch(
+		// 	() => state.setting.displayFingering,
+		// 	() => {
+		// 		if (state.fingeringInfo.direction === "vertical") {
+		// 			nextTick(() => {
+		// 				resetMusicScore();
+		// 			});
+		// 		}
+		// 	}
+		// );
 		// 监听播放状态
 		watch(
 			() => state.playState,
@@ -203,10 +204,10 @@ export default defineComponent({
 			}
 		);
 		onMounted(() => {
-			window.addEventListener("resize", resetMusicScore);
+			// window.addEventListener("resize", resetMusicScore);
 		});
 		onBeforeUnmount(() => {
-			window.removeEventListener("resize", resetMusicScore);
+			// window.removeEventListener("resize", resetMusicScore);
 		});
 		return () => (
 			<div

+ 1 - 1
src/page-colexiu/header-top/image/arrow.svg

@@ -3,7 +3,7 @@
     <!-- Generator: Sketch 62 (91390) - https://sketch.com -->
     <title>编组</title>
     <desc>Created with Sketch.</desc>
-    <g id="云练" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板备份" transform="translate(-212.000000, -29.000000)" fill="#01C1B5" fill-rule="nonzero">
             <g id="编组-4" transform="translate(51.000000, 17.000000)">
                 <g id="编组" transform="translate(165.333333, 18.000000) rotate(-360.000000) translate(-165.333333, -18.000000) translate(161.000000, 12.000000)">

+ 14 - 14
src/page-gym/detail/index.tsx

@@ -6,7 +6,7 @@ import Metronome, { metronomeData } from "../../helpers/metronome";
 import state, { handleSetSpeed, isRhythmicExercises } from "/src/state";
 import { storeData } from "../../store";
 import AudioList from "../../view/audio-list";
-import MusicScore, { resetMusicScore } from "../../view/music-score";
+import MusicScore from "../../view/music-score";
 import { sysMusicScoreAccompanimentQueryPage, sysMusicScoreCategoriesQueryTree } from "../api";
 import EvaluatModel from "../evaluat-model";
 import HeaderTop from "../header-top";
@@ -255,22 +255,22 @@ export default defineComponent({
 			};
 		});
 
-		// 监听指法显示
-		watch(
-			() => state.setting.displayFingering,
-			() => {
-				if (state.fingeringInfo.direction === "vertical") {
-					nextTick(() => {
-						resetMusicScore();
-					});
-				}
-			}
-		);
+		// // 监听指法显示
+		// watch(
+		// 	() => state.setting.displayFingering,
+		// 	() => {
+		// 		if (state.fingeringInfo.direction === "vertical") {
+		// 			nextTick(() => {
+		// 				resetMusicScore();
+		// 			});
+		// 		}
+		// 	}
+		// );
 		onMounted(() => {
-			window.addEventListener("resize", resetMusicScore);
+			//window.addEventListener("resize", resetMusicScore);
 		});
 		onBeforeUnmount(() => {
-			window.removeEventListener("resize", resetMusicScore);
+			//window.removeEventListener("resize", resetMusicScore);
 		});
 		return () => (
 			<div class={[styles.detail, state.setting.camera && styles.opencamera]} style={{ paddingLeft: detailData.paddingLeft }}>

+ 1 - 1
src/page-gym/evaluat-model/evaluat-share/index.tsx

@@ -43,7 +43,7 @@ export default defineComponent({
 		const shareNext = async () => {
 			const res = await api_shareAchievements({
 				title: "分享我的乐器练习进度,一起见证我的成长!",
-				desc: "晒一下我的评测分数,快来“云练”上和我PK一下吧!",
+				desc: "晒一下我的评测分数,快来“云练”上和我PK一下吧!",
 				image: shareLoadedPngData.value,
 				video: "",
 				type: "image",

+ 1 - 1
src/page-gym/evaluat-model/icons/arrow-left-background.svg

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <title>箭头</title>
-    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板" transform="translate(-21.000000, -19.000000)">
             <g id="箭头" transform="translate(21.000000, 19.000000)">
                 <circle id="椭圆形" fill="#01C1B5" cx="16" cy="16" r="16"></circle>

+ 1 - 1
src/page-gym/header-top/image/arrow.svg

@@ -3,7 +3,7 @@
     <!-- Generator: Sketch 62 (91390) - https://sketch.com -->
     <title>编组</title>
     <desc>Created with Sketch.</desc>
-    <g id="云练" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板备份" transform="translate(-212.000000, -29.000000)" fill="#01C1B5" fill-rule="nonzero">
             <g id="编组-4" transform="translate(51.000000, 17.000000)">
                 <g id="编组" transform="translate(165.333333, 18.000000) rotate(-360.000000) translate(-165.333333, -18.000000) translate(161.000000, 12.000000)">

+ 1 - 1
src/page-gym/header-top/image/icon-back.svg

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板" transform="translate(-27.000000, -123.000000)">
             <g id="返回箭头备份" transform="translate(43.000000, 139.000000) scale(-1, 1) translate(-43.000000, -139.000000) translate(27.000000, 123.000000)">
                 <circle id="椭圆形" fill="#FFFFFF" cx="16" cy="16" r="16"></circle>

+ 1 - 1
src/page-gym/header-top/image/menu.svg

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="34px" height="34px" viewBox="0 0 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <title>编组 5备份 4</title>
-    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板" transform="translate(-587.000000, -55.000000)">
             <g id="编组-5备份-4" transform="translate(589.000000, 57.000000)">
                 <rect id="矩形" stroke="#8EE1DC" stroke-width="2" fill="#FFFFFF" x="-1" y="-1" width="32" height="32" rx="12"></rect>

+ 1 - 1
src/page-gym/header-top/image/replay.svg

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="34px" height="34px" viewBox="0 0 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <title>编组 5备份 4</title>
-    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板" transform="translate(-499.000000, -55.000000)">
             <g id="编组-5备份-4" transform="translate(501.000000, 57.000000)">
                 <rect id="矩形" stroke="#8EE1DC" stroke-width="2" fill="#FFFFFF" x="-1" y="-1" width="32" height="32" rx="12"></rect>

+ 1 - 1
src/page-gym/header-top/image/section0.svg

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="34px" height="34px" viewBox="0 0 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <title>编组 5备份</title>
-    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板" transform="translate(-176.000000, -55.000000)">
             <g id="编组-5备份" transform="translate(178.000000, 57.000000)">
                 <rect id="矩形" stroke="#8EE1DC" stroke-width="2" fill="#FFFFFF" x="-1" y="-1" width="32" height="32" rx="12"></rect>

+ 1 - 1
src/page-gym/header-top/image/section1.svg

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="34px" height="34px" viewBox="0 0 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <title>编组 5备份 3</title>
-    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板" transform="translate(-217.000000, -55.000000)">
             <g id="编组-5备份-3" transform="translate(219.000000, 57.000000)">
                 <rect id="矩形" stroke="#8EE1DC" stroke-width="2" fill="#FFFFFF" x="-1" y="-1" width="32" height="32" rx="12"></rect>

+ 1 - 1
src/page-gym/header-top/image/section2.svg

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="34px" height="34px" viewBox="0 0 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <title>编组 5备份 5</title>
-    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板" transform="translate(-258.000000, -55.000000)">
             <g id="编组-5备份-5" transform="translate(260.000000, 57.000000)">
                 <rect id="矩形" stroke="#8EE1DC" stroke-width="2" fill="#FFFFFF" x="-1" y="-1" width="32" height="32" rx="12"></rect>

+ 1 - 1
src/page-gym/header-top/image/speed.svg

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg width="34px" height="34px" viewBox="0 0 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <title>编组 5备份 4</title>
-    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="云练2版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
         <g id="画板" transform="translate(-549.000000, -55.000000)">
             <g id="编组-5备份-4" transform="translate(551.000000, 57.000000)">
                 <rect id="矩形" stroke="#8EE1DC" stroke-width="2" fill="#FFFFFF" x="-1" y="-1" width="32" height="32" rx="12"></rect>

+ 1 - 1
src/page-gym/musci-list/index.tsx

@@ -99,7 +99,7 @@ export default defineComponent({
 				musicData.vipShow = true;
 				return;
 			}
-			// 销毁云
+			// 销毁云练
 			api_cloudDestroy();
 
 			const searchStr = queryString.stringify({

+ 1 - 1
src/page-gym/musci-list/vipModel/index.tsx

@@ -11,7 +11,7 @@ export default defineComponent({
 			<div class={styles.fraction}>
 				<img class={styles.closeBtn} src={iconBack} onClick={() => emit('close')} />
 				<img class={styles.icon} src={iconVip} />
-				<div class={styles.tip}>您尚未开通云练服务,请联系乐团老师开通</div>
+				<div class={styles.tip}>您尚未开通云练服务,请联系乐团老师开通</div>
 				<div class={styles.btns}>
 					<div style={{ background: "var(--van-primary-color)" }} class={styles.btn} onClick={() => emit("close", true)}>
 						确认

+ 20 - 7
src/page-instrument/App.tsx

@@ -18,7 +18,7 @@ export default defineComponent({
     };
     const setUser = async () => {
       try {
-        const res = query.isCbs ? {code:200,data:{}} : await getUserInfo();
+        const res = query.isCbs ? { code: 200, data: {} } : await getUserInfo();
         if (res?.code === 5000) {
           const browserInfo = browser();
           showToast(res.message);
@@ -36,8 +36,8 @@ export default defineComponent({
               "*"
             );
             setTimeout(() => {
-              window.location.href = `${location.origin}/classroom`
-            }, 500)
+              window.location.href = `${location.origin}/classroom`;
+            }, 500);
           }
           return;
         }
@@ -70,20 +70,21 @@ export default defineComponent({
       setBehaviorId(getRandomKey());
     });
 
+    // 老师端云练习,上下键切资源
     const onKeyBoard = (e: KeyboardEvent) => {
-      if (e.code === "ArrowLeft") {
+      if (e.code === "ArrowUp") {
         window.parent.postMessage(
           {
             api: "documentBodyKeyup",
-            code: "ArrowLeft",
+            code: "ArrowUp",
           },
           "*"
         );
-      } else if (e.code === "ArrowRight") {
+      } else if (e.code === "ArrowDown") {
         window.parent.postMessage(
           {
             api: "documentBodyKeyup",
-            code: "ArrowRight",
+            code: "ArrowDown",
           },
           "*"
         );
@@ -94,6 +95,18 @@ export default defineComponent({
       _loading && document.body.removeChild(_loading);
       // console.log(query);
       if (query.platform == "pc") document.body.addEventListener("keyup", (e: KeyboardEvent) => onKeyBoard(e));
+
+      // 禁用右键菜单
+      document.addEventListener("contextmenu", function (event) {
+        // event.preventDefault();
+      });
+      // 禁用浏览器快捷键
+      document.addEventListener("keydown", function (event) {
+        // 屏蔽 F12 和 Ctrl+Shift+I
+        if (event.key === "F12" || (event.ctrlKey && event.shiftKey && event.key === "I") || (event.metaKey && event.altKey && event.key === "I")) {
+          event.preventDefault();
+        }
+      });
     });
 
     onUnmounted(() => {

+ 19 - 2
src/page-instrument/api.ts

@@ -39,6 +39,23 @@ export const sysSuggestionAdd = (data: any) => {
 export const api_musicPracticeRecordDetail = (recordId: string) => {
   return request.get("/musicPracticeRecord/detail/" + recordId);
 };
+
+/** 获取声部 */
+export const api_subjectList = (params: any) => {
+  return request.post("/subject/list", {
+    data: params,
+    requestType: "json",
+  });
+};
+
+/** 获取乐器列表 */
+export const api_musicalInstrumentList = (params: any) => {
+  return request.post("/musicalInstrument/list", {
+    data: params,
+    requestType: "json",
+  });
+};
+
 /** 获取曲谱列表 */
 export const api_musicSheetPage = (data: any) => {
   return request.post("/musicSheet/page", {
@@ -61,8 +78,8 @@ export const getSubjectList = (data: any) => {
  * 获取意见类型
  */
 export const getSuggestionList = (params: any) => {
-  return request.post('/sysSuggestionType/page', {
+  return request.post("/sysSuggestionType/page", {
     data: params,
     requestType: "json",
   });
-};
+};

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

@@ -0,0 +1,41 @@
+.authorName{
+    height: 2.4rem;
+}
+.title{
+    width: 216px;
+    margin: 0 auto;
+    :global{
+        .van-notice-bar{
+            height: 30px;
+            line-height: 30px;
+            padding: 0;
+            font-weight: 600;
+            font-size: 18px;
+            color: #FFFFFF;
+            .van-notice-bar__wrap{
+                justify-content: center;
+            }
+        }
+    }
+}
+.authorCon{
+    display: flex;
+    justify-content: flex-end;
+    padding-right: 30px;
+    .author{
+        width: 210px;
+        :global{
+            .van-notice-bar{
+                height: 20px;
+                padding: 0;
+                font-weight: 400;
+                font-size: 13px;
+                color: #FFFFFF;
+                line-height: 20px;
+                .van-notice-bar__wrap{
+                    justify-content: flex-end;
+                }
+            }
+        }
+    }
+}

+ 42 - 0
src/page-instrument/component/authorName/index.tsx

@@ -0,0 +1,42 @@
+import { defineComponent, computed } from "vue"
+import styles from "./index.module.less"
+import { NoticeBar } from "vant"
+import state from "/src/state"
+import { smoothAnimationState } from "../../view-detail/smoothAnimation"
+
+export default defineComponent({
+   name: "authorName",
+   setup() {
+		const combineAuthor = computed(() => {
+			const context = state.musicLyricist ? state.musicComposer + ' / ' + state.musicLyricist : state.musicComposer;
+			return context
+		});
+
+      return () => (
+         <>
+            {
+               !smoothAnimationState.isShow.value && 
+               <div class={["authorName", styles.authorName]}>
+                  <div class={styles.title}>
+                     <NoticeBar text={state.examSongName} background="none" />
+                  </div>
+                  <div class={styles.authorCon}>
+                     <div class={styles.author}>
+                        {
+                           state.isSingleLine ? 
+                           <>
+                              { state.musicLyricist && <NoticeBar text={state.musicLyricist} background="none" />}
+                              { state.musicComposer && <NoticeBar text={state.musicComposer} background="none" /> }                           
+                           </> : 
+                           <>
+                              { combineAuthor.value && <NoticeBar text={combineAuthor.value} background="none" /> }
+                           </>
+                        }
+                     </div>
+                  </div>
+               </div>
+            }
+         </>
+      )
+   }
+})

+ 80 - 96
src/page-instrument/component/mode-type-mode/index.tsx

@@ -11,103 +11,87 @@ import { studentQueryUserInfo } from "../../api";
 import { usePageVisibility } from "@vant/use";
 import GuideIndex from "../../view-figner/guide/guide-index";
 import { getQuery } from "/src/utils/queryString";
+
 export default defineComponent({
-	name: "modelWraper",
+  name: "modelWraper",
 
-	setup() {
-		const query = getQuery();
-		const data = reactive({
-			showPC: false,
-			showStudent: false,
-			showVip: false,
-			showTip: false,
-		});
-		const openGuid = () => {
-			// 加载后 判断 端口号 加载对应的引导
-			if (storeData.platformType !== "STUDENT" || storeData.user.clientType !== "STUDENT") {
-				// PC
-				data.showPC = true;
-			} else {
-				if (storeData.user.vipMember) {
-					// 学生端
-					data.showStudent = true;
-				} else {
-					// vip
-					data.showVip = true;
-				}
-			}
-		};
+  setup() {
+    const query = getQuery();
+    const data = reactive({
+      showPC: false,
+      showStudent: false,
+      showVip: false,
+      showTip: false,
+    });
+    const openGuid = () => {
+      // 加载后 判断 端口号 加载对应的引导
+      if (storeData.platformType !== "STUDENT" || storeData.user.clientType !== "STUDENT") {
+        // PC
+        data.showPC = true;
+      } else {
+        // 从课堂乐器学生端课件预览默认不显示会员
+        if (storeData.user.vipMember || state.paymentType === "FREE" || query.showCourseMember === "true") {
+          // 学生端
+          data.showStudent = true;
+        } else {
+          // vip
+          data.showVip = true;
+        }
+      }
+    };
 
-		const getUserInfo = async () => {
-			const res = await studentQueryUserInfo();
-			const student = res?.data || {};
-			storeData.user.vipMember = student.vipMember;
-			// console.log("🚀 ~ student:", student);
-			if (storeData.user.vipMember) {
-				data.showVip = false;
-				openGuid();
-			}
-		};
-		const pageVisible = usePageVisibility();
-		watch(
-			() => pageVisible.value,
-			(val) => {
-				if (val === "visible") {
-					if (storeData.user.vipMember) return;
-					console.log("页面显示");
-					getUserInfo();
-				}
-			}
-		);
-		watch(
-			() => headTopData.modeType,
-			(val) => {
-				if (val === "init") {
-					nextTick(() => {
-						nextTick(() => {
-							setTimeout(() => {
-								data.showTip = true;
-							}, 500);
-						});
-					});
-				}
-			}
-		);
-		onMounted(() => {
-			openGuid();
-		});
-		return () => (
-			<>
-				<div class={[styles.wrap, headTopData.modeType === "init" ? "" : styles.hidden]}>
-					<div id="modeType-box" class={styles.infoWrap}>
-						<img
-							id="modeType-0"
-							onClick={() => headTopData.handleChangeModeType("practise")}
-							src={icons.icon_1}
-						/>
-						<img
-							id="modeType-1"
-							style={{ cursor: state.isPercussion ? "not-allowed" : "pointer" }}
-							onClick={() => headTopData.handleChangeModeType("follow")}
-							src={state.isPercussion ? icons.icon_5 : icons.icon_2}
-						/>
-						<img
-							id="modeType-2"
-							style={{ cursor: state.enableEvaluation ? "pointer" : "not-allowed" }}
-							onClick={() => headTopData.handleChangeModeType("evaluating")}
-							src={state.enableEvaluation ? icons.icon_3 : icons.icon_4}
-						/>
-					</div>
-					{data.showPC && data.showTip && !query.isCbs ? <TeacherBootom></TeacherBootom> : null}
-					{data.showStudent && data.showTip && !query.isCbs ? <StudentBottom></StudentBottom> : null}
-					{data.showVip && <TheVip />}
-				</div>
-				{headTopData.modeType &&
-					headTopData.modeType !== "init" &&
-					state.modeType == "practise" &&
-					state.fingeringInfo?.name &&
-					state.setting.displayFingering && <GuideIndex list={["detail"]} />}
-			</>
-		);
-	},
+    const getUserInfo = async () => {
+      const res = await studentQueryUserInfo();
+      const student = res?.data || {};
+      storeData.user.vipMember = student.vipMember;
+      // console.log("🚀 ~ student:", student);
+      if (storeData.user.vipMember) {
+        data.showVip = false;
+        openGuid();
+      }
+    };
+    const pageVisible = usePageVisibility();
+    watch(
+      () => pageVisible.value,
+      (val) => {
+        if (val === "visible") {
+          if (storeData.user.vipMember) return;
+          console.log("页面显示");
+          getUserInfo();
+        }
+      }
+    );
+    watch(
+      () => headTopData.modeType,
+      (val) => {
+        if (val === "init") {
+          nextTick(() => {
+            nextTick(() => {
+              setTimeout(() => {
+                data.showTip = true;
+              }, 500);
+            });
+          });
+        }
+      }
+    );
+    onMounted(() => {
+      openGuid();
+    });
+    return () => (
+      <>
+        <div class={[styles.wrap, headTopData.modeType === "init" ? "" : styles.hidden]}>
+          <div id="modeType-box" class={styles.infoWrap}>
+            <img id="modeType-0" onClick={() => headTopData.handleChangeModeType("practise")} src={icons.icon_1} />
+            <img id="modeType-1" style={{ cursor: state.isPercussion ? "not-allowed" : "pointer" }} onClick={() => headTopData.handleChangeModeType("follow")} src={state.isPercussion ? icons.icon_5 : icons.icon_2} />
+            <img id="modeType-2" style={{ cursor: state.enableEvaluation ? "pointer" : "not-allowed" }} onClick={() => headTopData.handleChangeModeType("evaluating")} src={state.enableEvaluation ? icons.icon_3 : icons.icon_4} />
+          </div>
+          {data.showPC && data.showTip && !query.isCbs ? <TeacherBootom></TeacherBootom> : null}
+          {data.showStudent && data.showTip && !query.isCbs ? <StudentBottom></StudentBottom> : null}
+          {data.showVip && <TheVip />}
+        </div>
+        {headTopData.modeType && headTopData.modeType !== "init" && state.modeType == "practise" && state.fingeringInfo?.name && state.setting.displayFingering && <GuideIndex list={["detail"]} />}
+      </>
+    );
+  },
 });

+ 3 - 0
src/page-instrument/component/the-comfirm/index.module.less

@@ -6,6 +6,9 @@
     background-color: #fff;
     border-radius: 18px;
     min-width: 244px;
+    &.pcFraction {
+        border-radius: 16PX;
+    }
 }
 
 .title {

+ 3 - 1
src/page-instrument/component/the-comfirm/index.tsx

@@ -3,6 +3,7 @@ import styles from "./index.module.less";
 import icon_title from '../../evaluat-model/evaluat-audio/icon_title.svg'
 import icon_cancel from '../../evaluat-model/evaluat-audio/icon_cancel.svg'
 import icon_confirm from '../../evaluat-model/evaluat-audio/icon_confirm.svg'
+import state, { IPlatform } from "/src/state";
 
 export default defineComponent({
 	name: "evaluat-audio",
@@ -15,7 +16,8 @@ export default defineComponent({
 	emits: ["close"],
 	setup(props, { emit }) {
 		return () => (
-			<div class={styles.fraction}>
+			<div class={[styles.fraction, state.platform === IPlatform.PC && styles.pcFraction]}>
+				{ state.platform === IPlatform.PC && <div class={'top_drag'}></div> }
 				<div class={styles.title}>
 					<img src={icon_title} />
 				</div>

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


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


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


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


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


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


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


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


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


+ 184 - 73
src/page-instrument/component/the-music-list/index.module.less

@@ -1,99 +1,210 @@
-.fixedBtn {
-    position: fixed;
-    top: 50%;
-    right: 0;
-    transform: translateY(-50%);
-    width: 22px;
-    height: 42px;
-    background: rgba(0, 0, 0, 0.24);
-    border-radius: 9px 0px 0px 9px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-
-    &:active {
-        opacity: .8;
-    }
-
-    &>img {
-        width: 12px;
-        height: 14px;
-    }
+.popup{
+    background:initial;
 }
-
 .tabs {
     height: 100vh;
-    width: 50vw;
-
+    width: 320px;
+    padding: 10px 0;
     :global {
-        .van-tabs {
+        .van-tabs{
             height: 100%;
-        }
-
-        .van-tabs__wrap {
-            height: 44px;
-            --van-tab-text-color: #777;
-
-            .van-tab {
-                z-index: 2;
+            .van-tabs__wrap{
+                display: flex;
+                justify-content: right;
+                height: 30px;
+                .van-tabs__nav--line{
+                    padding-bottom: 0;
+                    background-color: initial;
+                    .van-tab{
+                        flex: initial;
+                        .van-tab__text{
+                            display: none;
+                        }
+                        &:nth-child(1){
+                            width: 100px;
+                            height: 30px;
+                            background: url("./imgs/qtqm.png") no-repeat;
+                            background-size: 100% 100%;
+                            &.van-tab--active{
+                                background: url("./imgs/qtqmAct.png") no-repeat;
+                                background-size: 100% 100%;
+                            }
+                        }                   
+                        &:nth-child(2){
+                            margin-left: 24px;
+                            margin-right: 34px;
+                            width: 100px;
+                            height: 30px;
+                            background: url("./imgs/zjlx.png") no-repeat;
+                            background-size: 100% 100%;
+                            &.van-tab--active{
+                                background: url("./imgs/zjlxAct.png") no-repeat;
+                                background-size: 100% 100%;
+                            }
+                        }
+                    }
+                }
+                .van-tabs__line{
+                    display: none;
+                }
+            }
+            .van-tabs__content{
+                height: calc(100% - 30px);
+                background: url("./imgs/bg.png") no-repeat;
+                background-size: 100% 100%;
+                .van-tab__panel{
+                    height: 100%;
+                }
             }
         }
+    }
+}
 
-        .van-tabs__content {
-            height: calc(100% - 44px);
-            overflow: hidden;
+.wrap {
+    height: 100%;
+    padding: 18px 16px 12px 46px;
+    .searchBox{
+        display: flex;
+        align-items: center;
+        height: 34px;
+        background: #F8F9FC;
+        border-radius: 18px;
+        padding: 0 4px 0 12px;
+        border: 1px solid transparent;
+        &.isFocus{
+            border-color: #1CACF1;
         }
-
-        .van-tab__panel {
-            height: 100%;
-            overflow-x: hidden;
-            overflow-y: auto;
+        >img{
+            flex-shrink: 0;
+            width: 14px;
+            height: 14px;
         }
-        .van-tab--active::after{
-            content: '';
-            position: absolute;
-            bottom: 0;
-            left: 50%;
-            transform: translateX(-50%);
-            width: var(--van-tabs-bottom-bar-width);
-            height: var(--van-tabs-bottom-bar-height);
-            background: var(--van-tabs-bottom-bar-color);
-            border-radius: var(--van-tabs-bottom-bar-height);
+        :global{
+            .van-field{
+                margin: 0 6px;
+                flex-grow: 1;
+                padding: 0;
+                line-height: initial;
+                background: initial;
+                &::after{
+                    display: none;
+                }
+                .van-field__control{
+                    font-weight: 400;
+                    font-size: 13px;
+                    color: #131415;
+                    &::placeholder{
+                        color: #AAAAAA;
+                    }
+                }
+            }
         }
-        .van-tabs__line{
-            transition-duration: 0s !important;
-            display: none;
+        .searchBtn{
+            flex-shrink: 0;
+            width: 54px;
+            height: 26px; 
+            line-height: 26px;
+            text-align: center;
+            background: linear-gradient(to right,#5BECFF,#259CFE);
+            border-radius: 18px;
+            font-weight: 500;
+            font-size: 13px;
+            color: #FFFFFF;
+            cursor: pointer;
+            &:active{
+                opacity: 0.8;
+            }
+        }
+    }
+    :global{
+        .van-list{
+            margin-top: 10px;
+            height: calc(100% - 44px);
+            overflow-y: auto;
         }
     }
-}
-
-.wrap {
-    padding: 4px 12px;
 }
 
 .item {
     display: flex;
     align-items: center;
-    height: 41px;
-    border-radius: 7px;
-    font-size: 13px;
-    font-family: PingFangSC-Regular, PingFang SC;
-    font-weight: 400;
-    color: #333333;
-    line-height: 18px;
-    padding: 0 14px;
-    margin: 10px 0;
-}
-
-.itemActive {
-    background: #ECF9FF;
+    background: #FFFFFF;
+    border-radius: 16px;
+    padding: 6px;
+    margin-bottom: 8px;
+    &.itemActive {
+        background: linear-gradient( 180deg, #FFFFFF 0%, #BFE1FF 100%);
+        box-shadow: 0px 2px 4px 0px #499FE4;
+        border: 3px solid #FFFFFF;
+        .content .name{
+            color: #2EAAFE;
+        }
+    }
+    .titleImg {
+        width: 51px;
+        height: 51px;
+        margin-right: 10px;
+        border-radius: 9px !important;
+        overflow: hidden;
+        position: relative;
+        flex-shrink: 0;
+        .iconType {
+            position: absolute;
+            width: 28px;
+            height: 14px;
+            right: 0;
+            top: 0;
+            z-index: 9;
+            border-top-right-radius: 8px !important;
+            &.VIP {
+                background: url('./imgs/icon-music-vip.png') no-repeat center;
+                background-size: contain;
+            }
+        }
+    }
+    .content{
+        .name{
+            font-weight: 600;
+            font-size: 16px;
+            color: #333333;
+        }
+        .detail{
+            display: flex;
+            align-items: center;
+            margin-top: 8px;
+            .usedNum{
+                display: flex;
+                align-items: center;
+                padding: 3px 5px;
+                height: 17px;
+                background: #FFF8F7;
+                border-radius: 4px;
+                border: 1px solid #FFC5C5;
+                >img{
+                    width: 8px;
+                    height: 11px;
+                }
+                >div{
+                    margin-left: 3px;
+                    font-weight: 400;
+                    font-size: 12px;
+                    color: #FF6A6A
+                }
+            }
+            .author {
+                margin-left: 6px;
+                font-weight: 400;
+                font-size: 13px;
+                color: rgba(0,0,0,0.5);
+            }
+        }
+    }
 }
-.noData{
+.noData {
     display: flex;
     justify-content: center;
     align-items: center;
     height: 100%;
     font-size: 14px;
     color: #999999;
-    height: 70vh;
 }

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

@@ -1,21 +1,23 @@
-import { defineComponent, reactive } from "vue";
+import { computed, defineComponent, ref } from "vue";
 import styles from "./index.module.less";
-import icon_menu from "./icon_menu.png";
 import { Popup, Tab, Tabs } from "vant";
 import List from "./list";
+import { followData } from "/src/view/follow-practice";
+import state, {IPlatform} from "/src/state";
+import { evaluatingData } from "/src/view/evaluating";
+import { getQuery } from "/src/utils/queryString";
 
+const query: any = getQuery();
+export const isMusicList = computed(()=>{
+	return !(state.playState == "play" || followData.start || evaluatingData.startBegin || query.workRecord || query.modelType || state.platform === IPlatform.PC || query.isCbs)
+})
+export const musicListShow = ref(false)
 export default defineComponent({
 	name: "TheMusicList",
 	setup() {
-		const data = reactive({
-			show: false,
-		});
 		return () => (
 			<>
-				<div class={styles.fixedBtn} onClick={() => (data.show = true)}>
-					<img src={icon_menu} />
-				</div>
-				<Popup position="right" v-model:show={data.show} round>
+				<Popup class={styles.popup} position="left" v-model:show={musicListShow.value} round overlay-style={{background:'rgba(0, 0, 0, 0.3)'}}>
 					<div class={styles.tabs}>
 						<Tabs>
 							<Tab title="其他曲谱">

+ 119 - 94
src/page-instrument/component/the-music-list/list.tsx

@@ -2,104 +2,129 @@ 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 } from "vant";
+import { List, Image, Field } 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"
 
 export default defineComponent({
-	name: "TheMusicList-list",
-	props: {
-		recentFlag: {
-			type: Boolean,
-			default: false,
-		},
-	},
-	setup(props) {
-		const forms = reactive({
-			page: 1,
-			rows: 20,
-			musicSheetCategoriesId: state.bizMusicCategoryId,
-			recentFlag: props.recentFlag ? true : null,
-			excludeMusicId: props.recentFlag ? null : state.examSongId,
-		});
-		const data = reactive({
-			list: [] as any[],
-			finished: false,
-			loading: false,
-			hasNext: true,
-		});
-		const getList = async () => {
-			if (!data.hasNext) return
-			data.loading = true;
-			try {
-				const res = await api_musicSheetPage({
-					...forms,
-				});
-				if (res?.code === 200 && Array.isArray(res.data?.rows)) {
-					data.list = [...data.list, ...res.data.rows];
-				}
-				data.finished = res.data?.rows?.length < forms.rows;
-				data.hasNext = res.data?.total > data.list.length
-			} catch (error) {
-				console.log(error);
-			}
+  name: "TheMusicList-list",
+  props: {
+    recentFlag: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  setup(props) {
+    const forms = reactive({
+      name: "",
+      page: 1,
+      rows: 20,
+      musicSheetCategoriesId: state.bizMusicCategoryId,
+      recentFlag: props.recentFlag ? true : null,
+      excludeMusicId: props.recentFlag ? null : state.examSongId,
+    });
+    const data = reactive({
+      isFocus: false,
+      list: [] as any[],
+      finished: false,
+      loading: false,
+      hasNext: true,
+    });
+    const getList = async () => {
+      if (!data.hasNext) return;
+      data.loading = true;
+      try {
+        const res = await api_musicSheetPage({
+          ...forms,
+        });
+        if (res?.code === 200 && Array.isArray(res.data?.rows)) {
+          data.list = [...data.list, ...res.data.rows];
+        }
+        data.finished = res.data?.rows?.length < forms.rows;
+        data.hasNext = res.data?.total > data.list.length;
+      } catch (error) {
+        console.log(error);
+      }
 
-			data.loading = false;
-		};
-		watch(
-			() => props.recentFlag,
-			() => {
-				data.hasNext = true
-			}
-		);
-		onMounted(() => {
-			getList();
-		});
+      data.loading = false;
+    };
+    function handleQuery(){
+      forms.page = 1
+      forms.rows = 20
+      data.list = []
+      data.finished =false
+      data.loading = false
+      data.hasNext = true
+      getList()
+    }
+    watch(
+      () => props.recentFlag,
+      () => {
+        data.hasNext = true;
+      }
+    );
+    onMounted(() => {
+      getList();
+    });
 
-		const openAccomapina = (item: any) => {
-			if (item.id === state.examSongId) return;
-			// 暂停播放
-			togglePlay("paused");
-			postMessage({
-				api: "cloudLoading",
-				content: {
-					show: true,
-					type: "fullscreen",
-				},
-			});
-			location.href =
-				location.origin +
-				location.pathname +
-				"?" +
-				qs.stringify({
-					id: item.id,
-					_t: Date.now(),
-				});
-		};
-		return () => (
-			<div class={styles.wrap}>
-				<List
-					loading={data.loading}
-					finished={data.finished}
-					immediateCheck={false}
-					onLoad={() => {
-						forms.page++;
-						getList();
-					}}
-				>
-					{data.list.map((item: any) => {
-						return (
-							<div
-								class={[styles.item, state.examSongId == item.id && styles.itemActive]}
-								onClick={() => openAccomapina(item)}
-							>
-								{item.musicSheetName}
-							</div>
-						);
-					})}
-					{!data.loading && data.list.length === 0 && <div class={styles.noData}>暂无数据</div>}
-				</List>
-			</div>
-		);
-	},
+    const openAccomapina = (item: any) => {
+      if (item.id === state.examSongId) return;
+      // 暂停播放
+      togglePlay("paused");
+      postMessage({
+        api: "cloudLoading",
+        content: {
+          show: true,
+          type: "fullscreen",
+        },
+      });
+      location.href =
+        location.origin +
+        location.pathname +
+        "?" +
+        qs.stringify({
+          id: item.id,
+          _t: Date.now(),
+        });
+    };
+    return () => (
+      <div class={styles.wrap}>
+        <div class={[styles.searchBox,data.isFocus && styles.isFocus]}>
+          <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>
+        </div>
+        <List
+          loading={data.loading}
+          finished={data.finished}
+          immediateCheck={false}
+          onLoad={() => {
+            forms.page++;
+            getList();
+          }}
+        >
+          {data.list.map((item: any) => {
+            return (
+              <div class={[styles.item, state.examSongId == item.id && styles.itemActive]} onClick={() => openAccomapina(item)}>
+                <div class={styles.titleImg}>
+                  <i class={[styles.iconType, styles[item.paymentType]]}></i>
+                  <Image src={item.titleImg} class={styles.img} />
+                </div>
+                <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.composer && <p class={styles.author}>{item.composer}</p>}
+                  </div>
+                </div>
+              </div>
+            );
+          })}
+          {!data.loading && data.list.length === 0 && <div class={styles.noData}>暂无数据</div>}
+        </List>
+      </div>
+    );
+  },
 });

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


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


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


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


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


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


+ 57 - 15
src/page-instrument/custom-plugins/guide-page/student-bottom.tsx

@@ -5,6 +5,7 @@ import styles from "./index.module.less";
 import { getImage } from "./images";
 import { getQuery } from "/src/utils/queryString";
 import { getGuidance, setGuidance } from "./api";
+import state, { IPlatform } from "/src/state";
 
 export default defineComponent({
 	name: "studentB-guide",
@@ -13,7 +14,46 @@ export default defineComponent({
 		const data = reactive({
 			box: {},
 			show: false,
-			steps: [
+			steps: state.platform === IPlatform.PC ? [
+				{
+					ele: "",
+					eleRect: {} as DOMRect,
+					img: getImage("studentB1.png"),
+					handStyle: {
+						top: "-1.39rem",
+						left: "1.7rem",
+						transform: "rotate(180deg)",
+					},
+					imgStyle: {
+						top: "-5.01rem",
+						width: "6.48rem",
+						height: "3.01rem",
+						left: "1.2rem",
+					},
+					btnsStyle: {
+						top: "-1.61rem",
+						left: "3.2rem",
+					},
+				},
+				{
+					ele: "",
+					img: getImage("studentB2.png"),
+					handStyle: {
+						top: "-1.39rem",
+						left: "1.5rem",
+						transform: "rotate(180deg)",
+					},
+					imgStyle: {
+						top: "-5.01rem",
+						width: "6.48rem",
+						height: "3.01rem",
+					},
+					btnsStyle: {
+						top: "-1.61rem",
+						left: "2.3rem",
+					},
+				},
+			] : [
 				{
 					ele: "",
 					eleRect: {} as DOMRect,
@@ -85,21 +125,23 @@ export default defineComponent({
 		const guideInfo = ref({} as any)
 		const getAllGuidance = async()=>{
 		  try{
-		  const res = await getGuidance({guideTag:'guideInfo'})
-		  if(res.data){
-			guideInfo.value = JSON.parse(res.data?.guideValue) || null
-		  }else{
-			guideInfo.value = {}
-		  }
-	  
-	  
-		  if (guideInfo.value && guideInfo.value.studentB || !query.showGuide) {
-			tipShow.value = false;
-		  } else {
-			tipShow.value = true;
-		  }
+			if (state.guideInfo) {
+				guideInfo.value = state.guideInfo
+			} else {
+				const res = await getGuidance({guideTag:'guideInfo'})
+				if(res.data){
+				  guideInfo.value = JSON.parse(res.data?.guideValue) || null
+				}else{
+				  guideInfo.value = {}
+				}
+			}
+			if (guideInfo.value && guideInfo.value.studentB) {
+				tipShow.value = false;
+			} else {
+				tipShow.value = true;
+			}
 		  }catch(e){
-		  console.log(e)
+		  	console.log(e)
 		  }
 		  // const guideInfo = localStorage.getItem('teacher-guideInfo');
 	  

+ 62 - 48
src/page-instrument/custom-plugins/guide-page/student-top.tsx

@@ -7,6 +7,7 @@ import { useRoute } from "vue-router";
 import { getQuery } from "/src/utils/queryString";
 import { getGuidance, setGuidance } from "./api";
 import { headTopData } from "/src/page-instrument/header-top/index";
+import state from "/src/state";
 
 export default defineComponent({
   name: "studnetT-guide",
@@ -21,17 +22,17 @@ export default defineComponent({
           eleRect: {} as DOMRect,
           img: getImage("studnetT1.png"),
           handStyle: {
-            top: "1.3rem",
-            left:'0.3rem'
+            top: "0.67rem",
+            left:'0.1rem'
           },
           imgStyle: {
-            top: "1.9rem",
+            top: "1.2rem",
             width:'5.64rem',
             height:'2.77rem',
             left:'-2rem'
           },
           btnsStyle: {
-            top: "5.1rem",
+            top: "4.4rem",
             left:'-0.7rem'
           },
         },
@@ -39,18 +40,18 @@ export default defineComponent({
           ele: "",
           img: getImage("studnetT2.png"),
           handStyle: {
-            top: "1.3rem",
-            left:'0.3rem',
+            top: "0.67rem",
+            left:'0.1rem',
   
           },
           imgStyle: {
-            top: "1.9rem",
+            top: "1.2rem",
             width:'5.46rem',
             height:'2.3rem',
             left:'-2rem'
           },
           btnsStyle: {
-            top: "4.6rem",
+            top: "3.9rem",
             left:'-0.7rem'
           },
         },
@@ -58,17 +59,17 @@ export default defineComponent({
           ele: "",
           img: getImage("studnetT3.png"),
           handStyle: {
-            top: "1.3rem",
-            left:'0.3rem',
+            top: "0.67rem",
+            left:'0.1rem',
           },
           imgStyle: {
-            top: "1.9rem",
+            top: "1.2rem",
             width:'5.46rem',
             height:'2.30rem',
             left:'-2rem'
           },
           btnsStyle: {
-            top: "4.6rem",
+            top: "3.9rem",
             left:'-0.7rem'
           },
         },
@@ -76,17 +77,17 @@ export default defineComponent({
           ele: "",
           img: getImage("studnetT4.png"),
           handStyle: {
-            top: "1.3rem",
-            left:'0.3rem',
+            top: "0.67rem",
+            left:'0.1rem',
           },
           imgStyle: {
-            top: "1.8rem",
+            top: "1.2rem",
             width:'5.46rem',
             height:'2.30rem',
             left:'-2rem'
           },
           btnsStyle: {
-            top: "4.6rem",
+            top: "3.9rem",
             left:'-0.7rem'
           },
         },
@@ -94,18 +95,18 @@ export default defineComponent({
           ele: "",
           img: getImage("studnetT5.png"),
           handStyle: {
-            top: "1.3rem",
-            left:'0.3rem',
+            top: "0.67rem",
+            left:'0.1rem',
      
           },
           imgStyle: {
-            top: "1.8rem",
+            top: "1.2rem",
             width:'5.46rem',
             height:'2.6rem',
             left:'-3rem'
           },
           btnsStyle: {
-            top: "4.8rem",
+            top: "4.1rem",
             left:'-1.3rem'
           },
         },
@@ -113,18 +114,18 @@ export default defineComponent({
           ele: "",
           img: getImage("studnetT6.png"),
           handStyle: {
-            top: "1.3rem",
-            left:'0.3rem',
+            top: "0.67rem",
+            left:'0.1rem',
          
           },
           imgStyle: {
-            top: "1.62rem",
+            top: "0.92rem",
             width:'5.46rem',
             height:'2.30rem',
             left:'-5.5rem'
           },
           btnsStyle: {
-            top: "4.3rem",
+            top: "3.6rem",
             left:'-1.8rem'
           },
         },
@@ -133,17 +134,17 @@ export default defineComponent({
             eleRect: {} as DOMRect,
             img: getImage("studnetT7.png"),
             handStyle: {
-                top: "1.3rem",
-                left:'0.3rem',
+                top: "0.67rem",
+                left:'0.1rem',
             },
             imgStyle: {
-              top: "1.62rem",
+              top: "0.92rem",
               width:'5.46rem',
               height:'2.3rem',
               left:'-5.5rem'
             },
             btnsStyle: {
-              top: "4.3rem",
+              top: "3.6rem",
               left:'-3.7rem'
             },
           },      {
@@ -152,7 +153,7 @@ export default defineComponent({
             img: getImage("studnetT8.png"),
             handStyle: {
               top: "-1.38rem",
-              left:'.45rem',
+              left:'.26rem',
               transform: 'rotate(180deg)'
             },
             imgStyle: {
@@ -168,6 +169,7 @@ export default defineComponent({
           },
       ],
       step: 0,
+      initDone: false,
     });
     const tipShow = ref(false)
     const query: any = getQuery();
@@ -180,22 +182,25 @@ export default defineComponent({
 
   const guideInfo = ref({} as any)
   const getAllGuidance = async()=>{
+    console.log('学生引导123')
     try{
-    const res = await getGuidance({guideTag:'guideInfo'})
-    if(res.data){
-      guideInfo.value = JSON.parse(res.data?.guideValue) || null
-    }else{
-      guideInfo.value = {}
-    }
-
-
-    if (guideInfo.value && guideInfo.value.studnetT || !query.showGuide) {
-      tipShow.value = false;
-    } else {
-      tipShow.value = headTopData.modeType !== "init" ? true : false;
-    }
-    }catch(e){
-    console.log(e)
+			if (state.guideInfo) {
+				guideInfo.value = state.guideInfo
+			} else {
+				const res = await getGuidance({guideTag:'guideInfo'})
+				if(res.data){
+				  guideInfo.value = JSON.parse(res.data?.guideValue) || null
+				}else{
+				  guideInfo.value = {}
+				}
+			}
+      if (guideInfo.value && guideInfo.value.studnetT) {
+        tipShow.value = false;
+      } else {
+        tipShow.value = headTopData.modeType !== "init" ? true : false;
+      }
+    } catch(e) {
+      console.log(e)
     }
     // const guideInfo = localStorage.getItem('teacher-guideInfo');
 
@@ -211,6 +216,7 @@ export default defineComponent({
 					return
 				}
         const eleRect = ele.getBoundingClientRect();
+        // console.log(eleRect,666)
         data.box = {
           left: eleRect.x + "px",
           top: eleRect.y + "px",
@@ -220,7 +226,11 @@ export default defineComponent({
       }
     };
     onMounted(() => {
-      getStepELe();
+      // 顶部栏有个动画过度效果,位置会移动,需要加个延迟时间计算其移动后的位置
+      setTimeout(() => {
+        getStepELe();
+        data.initDone = true;
+      }, 1200);
     });
 
 
@@ -246,6 +256,7 @@ export default defineComponent({
 			  }catch(e){
 				console.log(e)
 			  }
+      state.guideInfo = guideInfo.value;
       tipShow.value = false
     //  localStorage.setItem('endC')
     }
@@ -262,8 +273,9 @@ export default defineComponent({
         
           跳过
         </div>} 
-        
-        <div class={styles.box} style={data.box} id={`modeType-${data.step}`}>
+        {
+          data.initDone &&
+          <div class={styles.box} style={data.box} id={`modeType-${data.step}`}>
           {data.steps.map((item: any, index) => (
 
             <div
@@ -306,7 +318,9 @@ export default defineComponent({
               </div>
             </div>
           ))}
-        </div>
+          </div>          
+        }
+
       </div>
       </Popup>
     );

+ 53 - 10
src/page-instrument/custom-plugins/guide-page/teacher-bootom.tsx

@@ -6,6 +6,7 @@ import { getImage } from "./images";
 import { getQuery } from "/src/utils/queryString";
 import {getGuidance,setGuidance} from './api'
 import { headTopData } from "/src/page-instrument/header-top/index";
+import state, { IPlatform } from "/src/state";
 
 export default defineComponent({
 	name: "aiTeacher-guide",
@@ -14,7 +15,46 @@ export default defineComponent({
 		const data = reactive({
 			box: {},
 			show: false,
-			steps: [
+			steps: state.platform === IPlatform.PC ? [
+				{
+					img: getImage("aiTeacher2.png"),
+					eleRect: {
+						top: "-3rem",
+					},
+					imgStyle: {
+						left: "-0.7rem",
+						width: "6.48rem",
+						height: "3.01rem",
+					},
+					btnsStyle: {
+						bottom: ".9rem",
+						left: ".9rem",
+						transform: "scale(.83)",
+					},
+					boxStyle: {
+						borderRadius: "40px",
+					},
+				},
+				{
+					img: getImage("aiTeacher3.png"),
+					eleRect: {
+						top: "-3rem",
+					},
+					imgStyle: {
+						left: "-0.7rem",
+						width: "6.48rem",
+						height: "3rem",
+					},
+					btnsStyle: {
+						bottom: ".9rem",
+						left: ".8rem",
+						transform: "scale(.83)",
+					},
+					boxStyle: {
+						borderRadius: "40px",
+					},
+				}
+			] : [
 				{
 					eleRect: {
 						left: "4.5rem",
@@ -110,15 +150,18 @@ export default defineComponent({
 		const guideInfo = ref({} as any)
 		const getAllGuidance = async()=>{
 		  try{
-			const res = await getGuidance({guideTag:'guideInfo'})
-			if(res.data){
-			  guideInfo.value = JSON.parse(res.data?.guideValue) || null
-			}else{
-			  guideInfo.value = {}
+			if (state.guideInfo) {
+				guideInfo.value = state.guideInfo
+			} else {
+				const res = await getGuidance({guideTag:'guideInfo'})
+				if(res.data){
+				  guideInfo.value = JSON.parse(res.data?.guideValue) || null
+				}else{
+				  guideInfo.value = {}
+				}
 			}
 	
-	
-			if (guideInfo.value && guideInfo.value.teacherBottom || !query.showGuide) {
+			if (guideInfo.value && guideInfo.value.teacherBottom) {
 			  tipShow.value = false;
 			} else {
 			  tipShow.value = headTopData.modeType === "init" ? true : false;
@@ -138,7 +181,7 @@ export default defineComponent({
 		// } else {
 		// 	tipShow.value = true;
 		// }
-		const steps = ["modeType-box", "modeType-0", "modeType-1", "modeType-2"];
+		const steps = state.platform === IPlatform.PC ? ["modeType-0", "modeType-1"] : ["modeType-box", "modeType-0", "modeType-1", "modeType-2"];
 		const getStepELe = () => {
 			console.log(steps[data.step]);
 			const ele: HTMLElement = document.getElementById(steps[data.step])!;
@@ -164,7 +207,7 @@ export default defineComponent({
 			getStepELe();
 		});
 		const handleNext = () => {
-			if (data.step >= 3) {
+			if (data.step >= 3 || (state.platform === IPlatform.PC && data.step >= 1)) {
 				endGuide();
 				return;
 			}

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