1
0

135 Revīzijas 5503e4aa56 ... ced2031dad

Autors SHA1 Ziņojums Datums
  TIANYONG ced2031dad build 4 mēneši atpakaļ
  TIANYONG d0e3202ea4 build 4 mēneši atpakaļ
  TIANYONG 58d53a572e build 4 mēneši atpakaļ
  TIANYONG d53b6ac96f Merge branch 'feature-patch' into gym-online 4 mēneši atpakaļ
  TIANYONG 1ac7cb4ef4 feat: 滑音点击区域修改 4 mēneši atpakaļ
  TIANYONG 8eb1974095 feat: 滑音点击区域修改 4 mēneši atpakaļ
  TIANYONG 854268e6ef build 4 mēneši atpakaļ
  TIANYONG 91f9f996ae Merge branch 'feature-patch' into gym-online 4 mēneši atpakaļ
  TIANYONG cb40a890c7 feat: 滑音点击区域待修改 4 mēneši atpakaļ
  TIANYONG 28b6160f57 Merge branch 'feature-patch' into gym-online 4 mēneši atpakaļ
  TIANYONG 3458a574ec feat: 缩小滑音音符的点击区域 4 mēneši atpakaļ
  TIANYONG 0735d409ef feat: 计算时值过滤掉和弦音符 4 mēneši atpakaļ
  TIANYONG 74a021e7d7 build 5 mēneši atpakaļ
  TIANYONG 9eab3b52d4 Merge branch 'feature-patch' into gym-online 5 mēneši atpakaļ
  TIANYONG 3ebd3ac9ea feat: 多轨修改 5 mēneši atpakaļ
  TIANYONG 99c4162bf3 feat: 多轨修改 5 mēneši atpakaļ
  TIANYONG 1abd94a37d build 5 mēneši atpakaļ
  TIANYONG 65e356a23f Merge branch 'feature-patch' into gym-online 5 mēneši atpakaļ
  TIANYONG 3cb9327ea7 feat: 手动点击音符不执行谱面滚动 5 mēneši atpakaļ
  TIANYONG 2dda67a5f7 feat: scrollTo滚动top值修改 5 mēneši atpakaļ
  TIANYONG 0f3303431b build 5 mēneši atpakaļ
  TIANYONG f240b35114 Merge branch 'feature-patch' into gym-online 5 mēneši atpakaļ
  TIANYONG 1784f558bc fest: 判断timeLog是否可用 5 mēneši atpakaļ
  TIANYONG 150c0a49df feat: osmd对象Instruments过滤掉common 5 mēneši atpakaļ
  TIANYONG a26aee8752 build 5 mēneši atpakaļ
  TIANYONG 39e9fc2a00 feat: 管理端进行延迟检测 5 mēneši atpakaļ
  TIANYONG 94672b33fe feat: 文案修改 5 mēneši atpakaļ
  TIANYONG 8adbaabe53 feat: 评测记录保存成功后再显示评测结果弹窗 5 mēneši atpakaļ
  TIANYONG 7e285c6eea feat: 评测记录保存成功后再显示评测结果弹窗 5 mēneši atpakaļ
  TIANYONG 0f88cb9f9a Merge branch 'gym-online' into feature-patch 5 mēneši atpakaļ
  TIANYONG 38bd53ad36 build 5 mēneši atpakaļ
  TIANYONG 587dd71205 Merge branch 'feature-tianyong' into gym-online 5 mēneši atpakaļ
  TIANYONG a061a9023e feat: noteId赋值修改 5 mēneši atpakaļ
  TIANYONG 6792e2a7ed feat: 评测报告兼容没有音频的情况 5 mēneši atpakaļ
  TIANYONG ef87abb12e fix: #12279问题5修复 5 mēneši atpakaļ
  TIANYONG 741ebd6bd4 feat: 单行谱页面不需要调用乐器code接口 5 mēneši atpakaļ
  TIANYONG 09569f065d feat: 先不调用api_closeCamera 5 mēneši atpakaļ
  TIANYONG bcc8a9f6af feat: 单行谱页面不需要调用乐器code接口 5 mēneši atpakaļ
  TIANYONG 866226f8b8 feat: api:recordAudioUpload,增加recordId参数 5 mēneši atpakaļ
  TIANYONG de498d9b96 feat: useNativeEvaluation赋值 5 mēneši atpakaļ
  TIANYONG e9cbba9b87 feat: 增加是否使用原生评测服务字段 5 mēneši atpakaļ
  TIANYONG 29c5273b12 feat: 增加recordAudioUpload 5 mēneši atpakaļ
  TIANYONG 0245188d02 style: 评测得分样式优化 5 mēneši atpakaļ
  TIANYONG b6262d8223 fix: #12279问题5修改 5 mēneši atpakaļ
  TIANYONG fab1443b96 build 5 mēneši atpakaļ
  TIANYONG 131f567759 Merge remote-tracking branch 'origin/hqyDev' into feature-tianyong 5 mēneši atpakaļ
  TIANYONG 4228389c4a feat: 选段非第一小节不播放系统节拍器 5 mēneši atpakaļ
  TIANYONG b079398276 fix: 转简谱基准时值逻辑修改 5 mēneši atpakaļ
  黄琪勇 04ad9dbdb4 切换音频的时候 设置音频时间 5 mēneši atpakaļ
  TIANYONG 27ae2051ac feat: 音频比选段的xml时值短兼容 5 mēneši atpakaļ
  TIANYONG e0b840aee1 fix: #12279第一、第二个问题修复 5 mēneši atpakaļ
  TIANYONG d0beb036ed fix: 12991、12992、12993bug修复 5 mēneši atpakaļ
  TIANYONG 037e435b1a feat: 选段模式兼容音频时长比xml时值短的情况 5 mēneši atpakaļ
  TIANYONG 0d0b77ca5f build 5 mēneši atpakaļ
  TIANYONG 4e42117111 Merge branch 'feature-tianyong' into gym-online 5 mēneši atpakaļ
  TIANYONG f1ca843890 feat: 部分手机performance.getEntriesByType('navigation')获取为空 5 mēneši atpakaļ
  TIANYONG df250fdfb7 feat: 异常上传修改 5 mēneši atpakaļ
  TIANYONG e1a6c3c6fa feat: 错误日志添加曲子业务id和partIndex 5 mēneši atpakaļ
  TIANYONG f6520b0b74 fix: 打击乐的评测报告显示内容错误修复 5 mēneši atpakaļ
  TIANYONG 60cb048343 feat: 错误日志上传修改 5 mēneši atpakaļ
  TIANYONG 66c83dcec9 feat: 添加异常错误上传 5 mēneši atpakaļ
  TIANYONG 9b5f2716f8 build 5 mēneši atpakaļ
  TIANYONG c809106439 Merge branch 'feature-tianyong' into gym-online 5 mēneši atpakaļ
  TIANYONG 6e9011120c fix: 评测记录提交两次问题修复 5 mēneši atpakaļ
  TIANYONG a7d6fa9c99 feat: 声轨名称匹配逻辑修改 5 mēneši atpakaļ
  TIANYONG 253d5c5d74 build 5 mēneši atpakaļ
  TIANYONG 926011c0b1 feat: 谱面滚动scrollTo,动画修改 5 mēneši atpakaļ
  TIANYONG 9ccf86cbc0 feat: 作业切换声轨 5 mēneši atpakaļ
  TIANYONG 3018d2cb56 build 6 mēneši atpakaļ
  TIANYONG 890236304a feat: 评测合并休止小节显示得分 6 mēneši atpakaļ
  TIANYONG 663301fda5 feat: 评测选段BeatLength修改 6 mēneši atpakaļ
  TIANYONG 6125dc0628 feat: 初始速度为小数问题 6 mēneši atpakaļ
  TIANYONG 5b09337095 feat: 修改 6 mēneši atpakaļ
  TIANYONG c9afbb5830 feat: 声轨名称匹配规则修改 6 mēneši atpakaļ
  TIANYONG 1b54891d14 feat: 声轨名称匹配规则修改 6 mēneši atpakaļ
  TIANYONG 773b2c4658 feat: 修改 6 mēneši atpakaļ
  TIANYONG 5b44ced1bb feat: 使用多个code匹配指法 6 mēneši atpakaļ
  TIANYONG 99b988d18c Merge branch 'feature-tianyong' into feature-patch 6 mēneši atpakaļ
  TIANYONG 3f55f225cf Merge branch 'gym-online' into feature-patch 6 mēneši atpakaļ
  TIANYONG 9e6ae7efd2 feat: 多轨渲染第一轨逻辑修改 6 mēneši atpakaļ
  TIANYONG 1487a8f56f feat: 计速度取整逻辑修改 6 mēneši atpakaļ
  TIANYONG ee53323bfd feat: 卸载评测模块,关闭摄像头 6 mēneši atpakaļ
  TIANYONG c18afdc278 feat: 谱面滚动scrollTo改为平滑:smooth 6 mēneši atpakaļ
  TIANYONG 0a18c3b2f8 build 6 mēneši atpakaļ
  TIANYONG c163367dd8 feat: 乐器指法逻辑优化 6 mēneši atpakaļ
  TIANYONG 5b65ee5166 feat: 声轨编码通过接口获取 6 mēneši atpakaļ
  TIANYONG 244eb81cb7 build 6 mēneši atpakaļ
  TIANYONG 3c517ff3e6 Merge branch 'feature-tianyong' into gym-online 6 mēneši atpakaļ
  TIANYONG 2b45631c72 feat: 大号指法,部分音符键位修改 6 mēneši atpakaļ
  黄琪勇 eccadd019c 修复节拍器合成绑定事件bug 6 mēneši atpakaļ
  TIANYONG 93824d8abe feat: 评测结果弹窗文案区分打击乐和非打击乐的曲子 6 mēneši atpakaļ
  TIANYONG e554c8d715 feat: 评测打开摄像头逻辑修改 6 mēneši atpakaļ
  TIANYONG e32b49ca53 fix: 查看报告 listen,play问题修复 6 mēneši atpakaļ
  TIANYONG 64a7adeb93 build 7 mēneši atpakaļ
  TIANYONG ad36f00072 feat: 打印console 7 mēneši atpakaļ
  TIANYONG d7a09842f4 feat: 切换声轨修改 7 mēneši atpakaļ
  TIANYONG 27af7fb339 fix: 从0开始的小节,评测合并休止小节数字问题修复 7 mēneši atpakaļ
  TIANYONG 95202a4450 fix: bug修复 7 mēneši atpakaļ
  TIANYONG 2412fe633a Merge remote-tracking branch 'origin/hqyDev' into feature-tianyong 7 mēneši atpakaļ
  黄琪勇 aee7fdc1d9 开启启用节拍器 7 mēneši atpakaļ
  TIANYONG 7941a52fd0 feat: 加载loading文案修改 7 mēneši atpakaļ
  TIANYONG a2cbe9701a feat: 去掉html加载loading 7 mēneši atpakaļ
  TIANYONG 2485e88dc1 style: 样式修改 7 mēneši atpakaļ
  TIANYONG 5be867a64c feat: 加载loading修改 7 mēneši atpakaļ
  TIANYONG cee66bb332 feat: 增加loading 7 mēneši atpakaļ
  TIANYONG 40678d2f02 feat: 详情页下载图片修改 7 mēneši atpakaļ
  TIANYONG ea4fffca87 feat: 渐快渐慢问题修复 7 mēneši atpakaļ
  黄琪勇 bd65b1f28e Merge branch 'gym-online' of http://git.dayaedu.com/tianyong/gym-music-score into hqyDev 7 mēneši atpakaļ
  TIANYONG b77016b2ab feat: 音频合成修改 7 mēneši atpakaļ
  TIANYONG 9076fd3f15 feat: 修改 7 mēneši atpakaļ
  TIANYONG 29b969a38b feat: 加载优化 7 mēneši atpakaļ
  TIANYONG 066224cbae feat: 切换声轨解析优化 7 mēneši atpakaļ
  TIANYONG 28c5110405 feat: 打包修改 7 mēneši atpakaļ
  TIANYONG 705e403d8a feat: 打包构建优化 7 mēneši atpakaļ
  TIANYONG 5a9302ecf7 feat: 修改 7 mēneši atpakaļ
  TIANYONG 76421555f0 build 7 mēneši atpakaļ
  TIANYONG 967b7ff21f Merge branch 'hoxfix-0110' into gym-online 7 mēneši atpakaļ
  TIANYONG 74d890cd79 feat: 增加渐快、渐慢标记 7 mēneši atpakaļ
  TIANYONG 8abffbde48 feat: 渐快渐慢标记添加 7 mēneši atpakaļ
  TIANYONG ebb8aea475 build 7 mēneši atpakaļ
  TIANYONG aaf061dd43 feat: 去掉特殊逻辑 7 mēneši atpakaļ
  TIANYONG 44b8500299 feat: 增加圆号code:Horns in F 7 mēneši atpakaļ
  TIANYONG 239d3e857f build 7 mēneši atpakaļ
  TIANYONG cad83ff58d feat: 删除渐变小节特殊处理 7 mēneši atpakaļ
  TIANYONG bbd239243d feat: 打印时间信息 7 mēneši atpakaļ
  TIANYONG c197755cc5 feat: 音频下载去掉时间戳 7 mēneši atpakaļ
  TIANYONG 43a275a166 Merge remote-tracking branch 'origin/hqyDev' into feature-tianyong 7 mēneši atpakaļ
  TIANYONG 90399990f6 feat: 使用indexedDB存储处理过后的xml 7 mēneši atpakaļ
  黄琪勇 933efbe952 改为前端按需合成节拍器 7 mēneši atpakaļ
  黄琪勇 870743d006 Merge branch 'gym-online' of http://git.dayaedu.com/tianyong/gym-music-score into hqyDev 7 mēneši atpakaļ
  黄琪勇 0bf6773609 合并 7 mēneši atpakaļ
  TIANYONG b241cab128 feat: 存储domparse解析后的对象 7 mēneši atpakaļ
  TIANYONG abee30658c build 7 mēneši atpakaļ
  TIANYONG b079014f7f feat: 打印时间信息 7 mēneši atpakaļ
  TIANYONG 8a2b4621e0 feat: 打印加载时间 7 mēneši atpakaļ
73 mainītis faili ar 1785 papildinājumiem un 723 dzēšanām
  1. 0 0
      dist/css/instrument-13bbe6a6.css
  2. 68 6
      dist/instrument.html
  3. 0 0
      dist/js/index-1c68c9ef.js
  4. 0 0
      dist/js/index-744b2334.js
  5. 1 0
      dist/js/index-7a3fc52b.js
  6. 0 1
      dist/js/index-85241191.js
  7. 0 0
      dist/js/index-94429a47.js
  8. 0 0
      dist/js/index-9711b760.js
  9. 0 0
      dist/js/index-eaa226c8.js
  10. 0 0
      dist/js/index-eac6fce2.js
  11. 1 0
      dist/js/index-legacy-06f360cc.js
  12. 0 0
      dist/js/index-legacy-0f5fed1a.js
  13. 0 1
      dist/js/index-legacy-10ae999f.js
  14. 0 0
      dist/js/index-legacy-4714a703.js
  15. 0 0
      dist/js/index-legacy-7eb6a4d7.js
  16. 0 0
      dist/js/index-legacy-7f49ecd6.js
  17. 0 0
      dist/js/index-legacy-99162798.js
  18. 0 0
      dist/js/index-legacy-e6511c52.js
  19. 0 0
      dist/js/instrument-8bb5186b.js
  20. 0 0
      dist/js/instrument-a7477007.js
  21. 0 0
      dist/js/instrument-legacy-13f55a15.js
  22. 0 0
      dist/js/instrument-legacy-31b75bc7.js
  23. 0 0
      dist/js/modeView-66c9abeb.js
  24. 0 0
      dist/js/modeView-95e5be2c.js
  25. 0 0
      dist/js/modeView-legacy-0863b937.js
  26. 0 0
      dist/js/modeView-legacy-4361bdf2.js
  27. 0 0
      dist/js/node_modules-30a54916.js
  28. 0 0
      dist/js/node_modules-legacy-55f273ae.js
  29. 0 0
      dist/js/polyfills-5d7eddce.js
  30. 0 0
      dist/js/polyfills-858fb2ee.js
  31. 0 0
      dist/js/polyfills-legacy-00a2b340.js
  32. 0 0
      dist/js/polyfills-legacy-7ea27e1e.js
  33. 0 0
      dist/js/src-cdcdb63f.js
  34. 0 0
      dist/js/src-legacy-c6e8bdad.js
  35. 61 1
      instrument.html
  36. 1 1
      osmd-extended
  37. 498 224
      src/constant/instruments.ts
  38. 1 1
      src/constant/instrumentsClassfiy.ts
  39. 8 0
      src/helpers/calcSpeed.ts
  40. 9 1
      src/helpers/communication.ts
  41. 0 25
      src/helpers/customMusicScore.ts
  42. 227 31
      src/helpers/formateMusic.ts
  43. 60 0
      src/hooks/errorLog/index.ts
  44. 42 0
      src/hooks/errorLog/uploadLog.ts
  45. 51 0
      src/page-instrument/App.tsx
  46. 1 0
      src/page-instrument/component/the-music-list/list.tsx
  47. 7 2
      src/page-instrument/custom-plugins/guide-driver/index.tsx
  48. 31 10
      src/page-instrument/evaluat-model/evaluat-result/index.tsx
  49. 47 14
      src/page-instrument/evaluat-model/index.tsx
  50. 10 7
      src/page-instrument/header-top/index.tsx
  51. 4 1
      src/page-instrument/header-top/settting/index.tsx
  52. 5 1
      src/page-instrument/header-top/speed/index.tsx
  53. 19 1
      src/page-instrument/main.ts
  54. 2 3
      src/page-instrument/simple-detail/index.tsx
  55. 43 14
      src/page-instrument/view-detail/index.tsx
  56. 14 64
      src/page-instrument/view-evaluat-report/index.tsx
  57. 145 251
      src/state.ts
  58. 15 0
      src/utils/baseApi.ts
  59. 7 1
      src/utils/index.ts
  60. 117 0
      src/utils/indexedDB.ts
  61. 1 1
      src/utils/request.ts
  62. 143 29
      src/view/audio-list/index.tsx
  63. 5 0
      src/view/evaluating/evaluatResult.ts
  64. 45 14
      src/view/evaluating/index.tsx
  65. 2 0
      src/view/fingering/fingering-config.ts
  66. 2 1
      src/view/fingering/fingering-relationships.ts
  67. 27 6
      src/view/music-score/index.tsx
  68. 1 1
      src/view/plugins/toggleMusicSheet/choosePartName/index.tsx
  69. 15 2
      src/view/plugins/toggleMusicSheet/index.tsx
  70. 3 3
      src/view/selection/index.module.less
  71. 17 5
      src/view/selection/index.tsx
  72. 0 0
      stats.html
  73. 29 0
      vite.config.ts

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/css/instrument-13bbe6a6.css


+ 68 - 6
dist/instrument.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-858fb2ee.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-5d7eddce.js"></script>
 
   <meta charset="UTF-8" />
   <meta name="viewport"
@@ -41,14 +41,76 @@
       })
     }
   </script>
-  <script type="module" crossorigin src="./js/instrument-8bb5186b.js"></script>
-  <link rel="stylesheet" href="./css/instrument-f4164af0.css">
+  <script type="module" crossorigin src="./js/instrument-a7477007.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/node_modules-30a54916.js">
+  <link rel="modulepreload" crossorigin href="./js/src-cdcdb63f.js">
+  <link rel="stylesheet" href="./css/instrument-13bbe6a6.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>
 
 <body>
-  <div id="app"></div>
+  <div id="app">
+    <!-- <style>
+      .firstLoading {
+        position: fixed;
+        left: 0;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        width: 100%;
+        height: 100%;
+        min-width: 100vw;
+        min-height: 100vh;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        z-index: 10000;
+      }
+      .firstLoading .loadingBox {
+        width: 27px;
+        height: 27px;
+        display: flex;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        align-content: space-between;
+        margin-bottom: 24px;
+        animation: rotate 1.5s linear infinite;
+      }
+      .firstLoading .loadingBox .loadingItem {
+        width: 11px;
+        height: 11px;
+        border-radius: 50%;
+        background: #06E7BE;
+        opacity: 0.5;
+      }
+      .firstLoading .loadingBox .loadingItem:nth-child(2) {
+        opacity: 1;
+      }
+      .firstLoading .loadingTip {
+        font-size: 14px;
+        color: #999;
+      }
+      @keyframes rotate {
+        from {
+          transform: rotate(0deg);
+        }
+        to {
+          transform: rotate(360deg);
+        }
+      }
+    </style>
+    <div class="firstLoading">
+      <div class="loadingBox">
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+      </div>
+      <div class="loadingTip">资源加载中,请稍后…</div>
+    </div> -->
+  </div>
   <!-- <img id="loading" class="show" src="/loading.svg" alt="loading" /> -->
   <!-- <script>
     // 处理课堂乐器老师端打开听音练习时去掉加载动画
@@ -65,8 +127,8 @@
     var vConsole = new window.VConsole();
   </script>   -->
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-7ea27e1e.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-31b75bc7.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-00a2b340.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-13f55a15.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-1c68c9ef.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-744b2334.js


+ 1 - 0
dist/js/index-7a3fc52b.js

@@ -0,0 +1 @@
+import{d as s,g as a,r as e,E as t,o,s as n,c as i,M as r}from"./instrument-a7477007.js";import"./node_modules-30a54916.js";import"./src-cdcdb63f.js";const d="_detail_vtlsh_12",l="_container_vtlsh_20",c=s({name:"music-list",setup(){const s=a(),c=e({isLoading:!0,isProductLoading:!1,product:[{state:!1,name:"五线谱",type:t.staff,base64:""},{state:!1,name:"首调",type:t.firstTone,base64:""},{state:!1,name:"固定调",type:t.fixedTone,base64:""}]});o((()=>{window.appName="colexiu",n.xmlUrl=s.xmlUrl,c.isLoading=!1}));const m=async()=>{console.log("渲染完成")};return()=>i("div",{class:d},[i("div",{id:"scrollContainer",class:[l,"hideCursor"]},[!c.isLoading&&i(r,{onRendered:m},null)])])}});export{c as default};

+ 0 - 1
dist/js/index-85241191.js

@@ -1 +0,0 @@
-import{d as i,g as l,r as d,E as e,o as r,s as c,b as s,M as u}from"./instrument-8bb5186b.js";const f="_skeleton_vtlsh_1",m="_detail_vtlsh_12",p="_container_vtlsh_20",a={skeleton:f,detail:m,container:p},y=i({name:"music-list",setup(){const n=l(),t=d({isLoading:!0,isProductLoading:!1,product:[{state:!1,name:"五线谱",type:e.staff,base64:""},{state:!1,name:"首调",type:e.firstTone,base64:""},{state:!1,name:"固定调",type:e.fixedTone,base64:""}]});r(()=>{window.appName="colexiu",c.xmlUrl=n.xmlUrl,t.isLoading=!1});const o=async()=>{console.log("渲染完成")};return()=>s("div",{class:a.detail},[s("div",{id:"scrollContainer",class:[a.container,"hideCursor"]},[!t.isLoading&&s(u,{onRendered:o},null)])])}});export{y as default};

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-94429a47.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-9711b760.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-eaa226c8.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-eac6fce2.js


+ 1 - 0
dist/js/index-legacy-06f360cc.js

@@ -0,0 +1 @@
+System.register(["./instrument-legacy-13f55a15.js","./node_modules-legacy-55f273ae.js","./src-legacy-c6e8bdad.js"],(function(e,t){"use strict";var n,i,a,o,s,r,l,d,c=document.createElement("style");return c.textContent="._skeleton_vtlsh_1{position:fixed;left:0;top:0;width:100vw;height:100vh;padding:.53333rem .8rem;background-color:#fff;z-index:1000;--van-skeleton-paragraph-height: .8rem}._detail_vtlsh_12{width:100vw;height:100vh;overflow:hidden;overflow-y:auto;--header-height: 1.65333rem;background:var(--container-background)}._detail_vtlsh_12 ._container_vtlsh_20{margin:0 .26667rem;border-radius:.26667rem}._detail_vtlsh_12 #musicAndSelection{overflow:initial!important;height:initial!important;max-height:initial!important}\n",document.head.appendChild(c),{setters:[e=>{n=e.d,i=e.g,a=e.r,o=e.E,s=e.o,r=e.s,l=e.c,d=e.M},null,null],execute:function(){const t="_detail_vtlsh_12",c="_container_vtlsh_20";e("default",n({name:"music-list",setup(){const e=i(),n=a({isLoading:!0,isProductLoading:!1,product:[{state:!1,name:"五线谱",type:o.staff,base64:""},{state:!1,name:"首调",type:o.firstTone,base64:""},{state:!1,name:"固定调",type:o.fixedTone,base64:""}]});s((()=>{window.appName="colexiu",r.xmlUrl=e.xmlUrl,n.isLoading=!1}));const h=async()=>{console.log("渲染完成")};return()=>l("div",{class:t},[l("div",{id:"scrollContainer",class:[c,"hideCursor"]},[!n.isLoading&&l(d,{onRendered:h},null)])])}}))}}}));

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-legacy-0f5fed1a.js


+ 0 - 1
dist/js/index-legacy-10ae999f.js

@@ -1 +0,0 @@
-System.register(["./instrument-legacy-31b75bc7.js"],(function(e,t){"use strict";var i,n,a,o,r,s,l,d,c=document.createElement("style");return c.textContent="._skeleton_vtlsh_1{position:fixed;left:0;top:0;width:100vw;height:100vh;padding:.53333rem .8rem;background-color:#fff;z-index:1000;--van-skeleton-paragraph-height: .8rem}._detail_vtlsh_12{width:100vw;height:100vh;overflow:hidden;overflow-y:auto;--header-height: 1.65333rem;background:var(--container-background)}._detail_vtlsh_12 ._container_vtlsh_20{margin:0 .26667rem;border-radius:.26667rem}._detail_vtlsh_12 #musicAndSelection{overflow:initial!important;height:initial!important;max-height:initial!important}\n",document.head.appendChild(c),{setters:[e=>{i=e.d,n=e.g,a=e.r,o=e.E,r=e.o,s=e.s,l=e.b,d=e.M}],execute:function(){const t="_detail_vtlsh_12",c="_container_vtlsh_20";e("default",i({name:"music-list",setup(){const e=n(),i=a({isLoading:!0,isProductLoading:!1,product:[{state:!1,name:"五线谱",type:o.staff,base64:""},{state:!1,name:"首调",type:o.firstTone,base64:""},{state:!1,name:"固定调",type:o.fixedTone,base64:""}]});r((()=>{window.appName="colexiu",s.xmlUrl=e.xmlUrl,i.isLoading=!1}));const h=async()=>{console.log("渲染完成")};return()=>l("div",{class:t},[l("div",{id:"scrollContainer",class:[c,"hideCursor"]},[!i.isLoading&&l(d,{onRendered:h},null)])])}}))}}}));

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-legacy-4714a703.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-legacy-7eb6a4d7.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-legacy-7f49ecd6.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-legacy-99162798.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/index-legacy-e6511c52.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/instrument-8bb5186b.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/instrument-a7477007.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/instrument-legacy-13f55a15.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/instrument-legacy-31b75bc7.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/modeView-66c9abeb.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/modeView-95e5be2c.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/modeView-legacy-0863b937.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/modeView-legacy-4361bdf2.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/node_modules-30a54916.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/node_modules-legacy-55f273ae.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/polyfills-5d7eddce.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/polyfills-858fb2ee.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/polyfills-legacy-00a2b340.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/polyfills-legacy-7ea27e1e.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/src-cdcdb63f.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/src-legacy-c6e8bdad.js


+ 61 - 1
instrument.html

@@ -42,7 +42,67 @@
 </head>
 
 <body>
-  <div id="app"></div>
+  <div id="app">
+    <!-- <style>
+      .firstLoading {
+        position: fixed;
+        left: 0;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        width: 100%;
+        height: 100%;
+        min-width: 100vw;
+        min-height: 100vh;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        z-index: 10000;
+      }
+      .firstLoading .loadingBox {
+        width: 27px;
+        height: 27px;
+        display: flex;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        align-content: space-between;
+        margin-bottom: 24px;
+        animation: rotate 1.5s linear infinite;
+      }
+      .firstLoading .loadingBox .loadingItem {
+        width: 11px;
+        height: 11px;
+        border-radius: 50%;
+        background: #06E7BE;
+        opacity: 0.5;
+      }
+      .firstLoading .loadingBox .loadingItem:nth-child(2) {
+        opacity: 1;
+      }
+      .firstLoading .loadingTip {
+        font-size: 14px;
+        color: #999;
+      }
+      @keyframes rotate {
+        from {
+          transform: rotate(0deg);
+        }
+        to {
+          transform: rotate(360deg);
+        }
+      }
+    </style>
+    <div class="firstLoading">
+      <div class="loadingBox">
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+        <div class="loadingItem"></div>
+      </div>
+      <div class="loadingTip">资源加载中,请稍后…</div>
+    </div> -->
+  </div>
   <!-- <img id="loading" class="show" src="/loading.svg" alt="loading" /> -->
   <!-- <script>
     // 处理课堂乐器老师端打开听音练习时去掉加载动画

+ 1 - 1
osmd-extended

@@ -1 +1 @@
-Subproject commit 4ebb0ca539cdf162b77078d69830820764752a3d
+Subproject commit 647c6d5387e5d379740089499eaa32643e0d802f

+ 498 - 224
src/constant/instruments.ts

@@ -1,228 +1,465 @@
-const instruments: any = {
-	'Acoustic Grand Piano': '大钢琴',
-	'Bright Acoustic Piano': '明亮的钢琴',
-	'Electric Grand Piano': '电钢琴',
-	'Rhodes Piano': '柔和的电钢琴',
-	'Chorused Piano': '加合唱效果的电钢琴',
-	Harpsichord: '羽管键琴',
-	Clavichord: '科拉维科特琴',
-	Celesta: '钢片琴',
-	Glockenspiel: '钢片琴',
-	'Music box': '八音盒',
-	Vibraphone: '颤音琴',
-	Marimba: '马林巴',
-	Xylophone: '木琴',
-	'Tubular Bells': '管钟',
-	Dulcimer: '大扬琴',
-	'Hammond Organ': '击杆风琴',
-	'Percussive Organ': '打击式风琴',
-	'Rock Organ': '摇滚风琴',
-	'Church Organ': '教堂风琴',
-	'Reed Organ': '簧管风琴',
-	Accordian: '手风琴',
-	Harmonica: '口琴',
-	'Tango Accordian': '探戈手风琴',
-	'Acoustic Guitar': '钢弦吉他',
-	'Electric Guitar': '闷音电吉他',
-	'Overdriven Guitar': '加驱动效果的电吉他',
-	'Distortion Guitar': '加失真效果的电吉他',
-	'Guitar Harmonics': '吉他和音',
-	'Acoustic Bass': '大贝司',
-	'Electric Bass': '电贝司',
-	'Fretless Bass': '无品贝司',
-	'Slap Bass': '掌击',
-	'Synth Bass': '电子合成',
-	Violin: '小提琴',
-	Viola: '中提琴',
-	Cello: '大提琴',
-	Contrabass: '低音大提琴',
-	'Tremolo Strings': '弦乐群颤音音色',
-	'Pizzicato Strings': '弦乐群拨弦音色',
-	'Orchestral Harp': '竖琴',
-	Timpani: '定音鼓',
-	'String Ensemble': '弦乐合奏音色',
-	'Synth Strings': '合成弦乐合奏音色',
-	'Choir Aahs': '人声合唱',
-	'Voice Oohs': '人声',
-	'Synth Voice': '合成人声',
-	'Orchestra Hit': '管弦乐敲击齐奏',
-	Trumpet: '小号',
-	Trombone: '长号',
-	Tuba: '大号',
-	'Muted Trumpet': '加弱音器小号',
-	'French Horn': '法国号',
-	'Brass Section': '铜管组',
-	'Synth Brass': '合成铜管音色',
-	'Soprano Sax': '高音萨克斯管',
-	'Alto Sax': '中音萨克斯管',
-	'Tenor Sax': '次中音萨克斯管',
-	'Baritone Sax': '低音萨克斯管',
-	Oboe: '双簧管',
-	'English Horn': '英国管',
-	Bassoon: '巴松',
-	'Soprano Saxophone': '高音萨克斯管',
-	'Alto Saxophone': '中音萨克斯管',
-	'Tenor Saxophone': '次中音萨克斯管',
-	'Baritone Saxophone': '低音萨克斯管',
-	Piccolo: '短笛',
-	Flute: '长笛',
-	Recorder: '竖笛',
-	'Soprano Recorder': '高音竖笛',
-	'Pan Flute': '排箫',
-	'Bottle Blow': '瓶木管',
-	Whistle: '口哨声',
-	Ocarina: '陶笛',
-	Lead: '合成主音',
-	'Lead lead': '合成主音',
-	'Pad age': '合成音色',
-	Pad: '合成音色',
-	FX: '合成效果  科幻',
-	Sitar: '西塔尔',
-	Banjo: '班卓琴',
-	Shamisen: '三昧线',
-	Koto: '十三弦筝',
-	Kalimba: '卡林巴',
-	Bagpipe: '风笛',
-	Fiddle: '民族提琴',
-	Shanai: '山奈',
-	'Tinkle Bell': '叮当铃',
-	Agogos: '阿戈戈铃',
-	'Steel Drums': '钢鼓',
-	'Taiko Drum': '太鼓',
-	'Melodic Toms': '嗵嗵鼓',
-	'Synth Drums': '合成鼓',
-	'Reverse Cymbals': '反向镲',
-	'Agogo Bells': '阿戈戈铃',
-	'Taiko Drums': '太鼓',
-	Bongos: '邦戈鼓',
-	'Bongo Bell': '邦戈铃',
-	Congas: '康加鼓',
-	Guiro: '刮壶',
-	'Guitar Fret Noise': '吉他换把杂音',
-	'Breath Noise': '呼吸声',
-	Seashore: '海浪声',
-	'Bird Tweet': '鸟鸣',
-	'Telephone Ring': '电话铃',
-	Helicopter: '直升机',
-	Applause: '鼓掌声',
-	Gunshot: '枪声',
-	'Acoustic Bass Drum': '大鼓',
-	'Bass Drum': '大鼓',
-	'Side Drum': '小鼓鼓边',
-	'Acoustic Snare': '小鼓',
-	'Hand Claps': '拍手',
-	'Electric Snare': '小鼓',
-	'Low Floor Tom': '低音嗵鼓',
-	'Closed Hi-Hat': '闭合踩镲',
-	'High Floor Tom': '高音落地嗵鼓',
-	'Pedal Hi-Hat': '脚踏踩镲',
-	'Low Tom': '低音嗵鼓',
-	'Open Hi-Hat': '开音踩镲',
-	'Low-Mid Tom': '中低音嗵鼓',
-	'Hi Mid Tom': '高音鼓',
-	'Crash Cymbals': '对镲',
-	'High Tom': '高音嗵鼓',
-	'Ride Cymbals': '叮叮镲',
-	'Chinese Cymbals': '中国镲',
-	'Ride Bell': '圆铃',
-	Tambourine: '铃鼓',
-	'Splash Cymbal': '溅音镲',
-	Cowbell: '牛铃',
-	'Crash Cymbal': '强音钹',
-	'Vibra-Slap': '颤音器',
-	'Ride Cymbal': '打点钹',
-	'Hi Bongo': '高音邦戈鼓',
-	'Low Bongo': '低音邦戈鼓',
-	'Mute Hi Conga': '弱音高音康加鼓',
-	'Open Hi Conga': '强音高音康加鼓',
-	'Low Conga': '低音康加鼓',
-	'High Timbale': '高音天巴鼓',
-	'Low Timbale': '低音天巴鼓',
-	'High Agogo': '高音阿戈戈铃',
-	'Low Agogo': '低音阿戈戈铃',
-	Cabasa: '卡巴萨',
-	Maracas: '沙锤',
-	'Short Whistle': '短口哨',
-	'Long Whistle': '长口哨',
-	'Short Guiro': '短刮壶',
-	'Long Guiro': '长刮壶',
-	Claves: '响棒',
-	'Hi Wood Block': '高音木鱼',
-	'Low Wood Block': '低音木鱼',
-	'Mute Triangle': '弱音三角铁',
-	'Open Triangle': '强音三角铁',
-	'Drum Set': '架子鼓',
-	'Hulusi flute': '葫芦丝',
-	Melodica: '口风琴',
-	'Snare Drum': '小军鼓',
-	'Horn in F': '圆号',
-	Triangle: '三角铁',
-	Vibrato: '颤音琴',
-	'Suspend Cymbals': '吊镲',
-	'Suspended Cymbals': '吊镲',
-	'Tom-Toms': '嗵嗵鼓',
-	Bell: '铃铛',
-	Bells: '铃铛',
-	'Alto Clarinet': '中音单簧管',
-	'Bass Clarinet': '低音单簧管',
-	Clarinet: '单簧管',
-	Cornet: '短号',
-	Euphonium: '上低音号',
-	'crash cymbals': '对镲',
-	Castanets: '响板',
-	Shaker: '沙锤',
-	'Mark tree': '音树',
-	Chimes: '管钟',
-	'Mark Tree': '音树',
-	'Tom-toms': '嗵嗵鼓',
-	'Hi-Hat': '踩镲',
-	'Sleigh Bells': '雪橇铃',
-	Flexatone: '弹音器',
-	'Brake drum': '闸鼓',
-	Gong: '锣',
-	'concert tom': '音乐会嗵嗵鼓',
-	'brake drum': '车轮鼓',
-	'finger cymbal': '指钹',
-	'ride cymbal': '叮叮镲',
-	'Concert Toms': '音乐会嗵嗵鼓',
-	Vibraslap: '弹音器',
-	'Wood Blocks': '木鱼',
-	'Temple Blocks': '木鱼',
-	'Wood Block': '木鱼',
-	'Field Drum': '军鼓',
-	'Quad-Toms': '筒鼓',
-	Quads: '筒鼓',
-	'Drums set': '架子鼓',
-	'High Bongo': '邦戈',
-	Timbales: '天巴鼓',
-	'rain stick': '雨棒',
-	'String Bass': '弦乐低音',
-	'Floor Tom': '侧嗵鼓',
-	'Brake Drum': '闸鼓',
-	'Tam-tam': '大锣',
-	Cymbal: '镲',
-	Cymbals: '镲',
-	Whip: '乐鞭',
-	whip: '乐鞭'
-};
+// const instruments: any = {
+// 	'Acoustic Grand Piano': '大钢琴',
+// 	'Bright Acoustic Piano': '明亮的钢琴',
+// 	'Electric Grand Piano': '电钢琴',
+// 	'Rhodes Piano': '柔和的电钢琴',
+// 	'Chorused Piano': '加合唱效果的电钢琴',
+// 	Harpsichord: '羽管键琴',
+// 	Clavichord: '科拉维科特琴',
+// 	Celesta: '钢片琴',
+// 	Glockenspiel: '钢片琴',
+// 	'Music box': '八音盒',
+// 	Vibraphone: '颤音琴',
+// 	Marimba: '马林巴',
+// 	Xylophone: '木琴',
+// 	'Tubular Bells': '管钟',
+// 	Dulcimer: '大扬琴',
+// 	'Hammond Organ': '击杆风琴',
+// 	'Percussive Organ': '打击式风琴',
+// 	'Rock Organ': '摇滚风琴',
+// 	'Church Organ': '教堂风琴',
+// 	'Reed Organ': '簧管风琴',
+// 	Accordian: '手风琴',
+// 	Harmonica: '口琴',
+// 	'Tango Accordian': '探戈手风琴',
+// 	'Acoustic Guitar': '钢弦吉他',
+// 	'Electric Guitar': '闷音电吉他',
+// 	'Overdriven Guitar': '加驱动效果的电吉他',
+// 	'Distortion Guitar': '加失真效果的电吉他',
+// 	'Guitar Harmonics': '吉他和音',
+// 	'Acoustic Bass': '大贝司',
+// 	'Electric Bass': '电贝司',
+// 	'Fretless Bass': '无品贝司',
+// 	'Slap Bass': '掌击',
+// 	'Synth Bass': '电子合成',
+// 	Violin: '小提琴',
+// 	Viola: '中提琴',
+// 	Cello: '大提琴',
+// 	Contrabass: '低音大提琴',
+// 	'Tremolo Strings': '弦乐群颤音音色',
+// 	'Pizzicato Strings': '弦乐群拨弦音色',
+// 	'Orchestral Harp': '竖琴',
+// 	Timpani: '定音鼓',
+// 	'String Ensemble': '弦乐合奏音色',
+// 	'Synth Strings': '合成弦乐合奏音色',
+// 	'Choir Aahs': '人声合唱',
+// 	'Voice Oohs': '人声',
+// 	'Synth Voice': '合成人声',
+// 	'Orchestra Hit': '管弦乐敲击齐奏',
+// 	Trumpet: '小号',
+// 	Trombone: '长号',
+// 	Tuba: '大号',
+// 	'Muted Trumpet': '加弱音器小号',
+// 	'French Horn': '法国号',
+// 	'Brass Section': '铜管组',
+// 	'Synth Brass': '合成铜管音色',
+// 	'Soprano Sax': '高音萨克斯管',
+// 	'Alto Sax': '中音萨克斯管',
+// 	'Tenor Sax': '次中音萨克斯管',
+// 	'Baritone Sax': '低音萨克斯管',
+// 	Oboe: '双簧管',
+// 	'English Horn': '英国管',
+// 	Bassoon: '巴松',
+// 	'Soprano Saxophone': '高音萨克斯管',
+// 	'Alto Saxophone': '中音萨克斯管',
+// 	'Tenor Saxophone': '次中音萨克斯管',
+// 	'Baritone Saxophone': '低音萨克斯管',
+// 	Piccolo: '短笛',
+// 	Flute: '长笛',
+// 	Recorder: '竖笛',
+// 	'Soprano Recorder': '高音竖笛',
+// 	'Pan Flute': '排箫',
+// 	'Bottle Blow': '瓶木管',
+// 	Whistle: '口哨声',
+// 	Ocarina: '陶笛',
+// 	Lead: '合成主音',
+// 	'Lead lead': '合成主音',
+// 	'Pad age': '合成音色',
+// 	Pad: '合成音色',
+// 	FX: '合成效果  科幻',
+// 	Sitar: '西塔尔',
+// 	Banjo: '班卓琴',
+// 	Shamisen: '三昧线',
+// 	Koto: '十三弦筝',
+// 	Kalimba: '卡林巴',
+// 	Bagpipe: '风笛',
+// 	Fiddle: '民族提琴',
+// 	Shanai: '山奈',
+// 	'Tinkle Bell': '叮当铃',
+// 	Agogos: '阿戈戈铃',
+// 	'Steel Drums': '钢鼓',
+// 	'Taiko Drum': '太鼓',
+// 	'Melodic Toms': '嗵嗵鼓',
+// 	'Synth Drums': '合成鼓',
+// 	'Reverse Cymbals': '反向镲',
+// 	'Agogo Bells': '阿戈戈铃',
+// 	'Taiko Drums': '太鼓',
+// 	Bongos: '邦戈鼓',
+// 	'Bongo Bell': '邦戈铃',
+// 	Congas: '康加鼓',
+// 	Guiro: '刮壶',
+// 	'Guitar Fret Noise': '吉他换把杂音',
+// 	'Breath Noise': '呼吸声',
+// 	Seashore: '海浪声',
+// 	'Bird Tweet': '鸟鸣',
+// 	'Telephone Ring': '电话铃',
+// 	Helicopter: '直升机',
+// 	Applause: '鼓掌声',
+// 	Gunshot: '枪声',
+// 	'Acoustic Bass Drum': '大鼓',
+// 	'Bass Drum': '大鼓',
+// 	'Side Drum': '小鼓鼓边',
+// 	'Acoustic Snare': '小鼓',
+// 	'Hand Claps': '拍手',
+// 	'Electric Snare': '小鼓',
+// 	'Low Floor Tom': '低音嗵鼓',
+// 	'Closed Hi-Hat': '闭合踩镲',
+// 	'High Floor Tom': '高音落地嗵鼓',
+// 	'Pedal Hi-Hat': '脚踏踩镲',
+// 	'Low Tom': '低音嗵鼓',
+// 	'Open Hi-Hat': '开音踩镲',
+// 	'Low-Mid Tom': '中低音嗵鼓',
+// 	'Hi Mid Tom': '高音鼓',
+// 	'Crash Cymbals': '对镲',
+// 	'High Tom': '高音嗵鼓',
+// 	'Ride Cymbals': '叮叮镲',
+// 	'Chinese Cymbals': '中国镲',
+// 	'Ride Bell': '圆铃',
+// 	Tambourine: '铃鼓',
+// 	'Splash Cymbal': '溅音镲',
+// 	Cowbell: '牛铃',
+// 	'Crash Cymbal': '强音钹',
+// 	'Vibra-Slap': '颤音器',
+// 	'Ride Cymbal': '打点钹',
+// 	'Hi Bongo': '高音邦戈鼓',
+// 	'Low Bongo': '低音邦戈鼓',
+// 	'Mute Hi Conga': '弱音高音康加鼓',
+// 	'Open Hi Conga': '强音高音康加鼓',
+// 	'Low Conga': '低音康加鼓',
+// 	'High Timbale': '高音天巴鼓',
+// 	'Low Timbale': '低音天巴鼓',
+// 	'High Agogo': '高音阿戈戈铃',
+// 	'Low Agogo': '低音阿戈戈铃',
+// 	Cabasa: '卡巴萨',
+// 	Maracas: '沙锤',
+// 	'Short Whistle': '短口哨',
+// 	'Long Whistle': '长口哨',
+// 	'Short Guiro': '短刮壶',
+// 	'Long Guiro': '长刮壶',
+// 	Claves: '响棒',
+// 	'Hi Wood Block': '高音木鱼',
+// 	'Low Wood Block': '低音木鱼',
+// 	'Mute Triangle': '弱音三角铁',
+// 	'Open Triangle': '强音三角铁',
+// 	'Drum Set': '架子鼓',
+// 	'Hulusi flute': '葫芦丝',
+// 	Melodica: '口风琴',
+// 	'Snare Drum': '小军鼓',
+// 	'Horn in F': '圆号',
+// 	'Horns in F': '圆号',
+// 	Triangle: '三角铁',
+// 	Vibrato: '颤音琴',
+// 	'Suspend Cymbals': '吊镲',
+// 	'Suspended Cymbals': '吊镲',
+// 	'Tom-Toms': '嗵嗵鼓',
+// 	Bell: '铃铛',
+// 	Bells: '铃铛',
+// 	'Alto Clarinet': '中音单簧管',
+// 	'Bass Clarinet': '低音单簧管',
+// 	Clarinet: '单簧管',
+// 	Cornet: '短号',
+// 	Euphonium: '上低音号',
+// 	'crash cymbals': '对镲',
+// 	Castanets: '响板',
+// 	Shaker: '沙锤',
+// 	'Mark tree': '音树',
+// 	Chimes: '管钟',
+// 	'Mark Tree': '音树',
+// 	'Tom-toms': '嗵嗵鼓',
+// 	'Hi-Hat': '踩镲',
+// 	'Sleigh Bells': '雪橇铃',
+// 	Flexatone: '弹音器',
+// 	'Brake drum': '闸鼓',
+// 	Gong: '锣',
+// 	'concert tom': '音乐会嗵嗵鼓',
+// 	'brake drum': '车轮鼓',
+// 	'finger cymbal': '指钹',
+// 	'ride cymbal': '叮叮镲',
+// 	'Concert Toms': '音乐会嗵嗵鼓',
+// 	Vibraslap: '弹音器',
+// 	'Wood Blocks': '木鱼',
+// 	'Temple Blocks': '木鱼',
+// 	'Wood Block': '木鱼',
+// 	'Field Drum': '军鼓',
+// 	'Quad-Toms': '筒鼓',
+// 	Quads: '筒鼓',
+// 	'Drums set': '架子鼓',
+// 	'High Bongo': '邦戈',
+// 	Timbales: '天巴鼓',
+// 	'rain stick': '雨棒',
+// 	'String Bass': '弦乐低音',
+// 	'Floor Tom': '侧嗵鼓',
+// 	'Brake Drum': '闸鼓',
+// 	'Tam-tam': '大锣',
+// 	Cymbal: '镲',
+// 	Cymbals: '镲',
+// 	Whip: '乐鞭',
+// 	whip: '乐鞭'
+// };
+/** 获取分轨名称 */
+
+// 乐器code码
+export const musicalInstrumentCodeInfo = [
+	{
+	  name: '长笛',
+	  code: 'Flute',
+	  id: 1
+	},
+	{
+	  name: '短笛',
+	  code: 'Piccolo',
+	  id: 2
+	},
+	{
+	  name: '单簧管',
+	  code: 'Clarinet',
+	  id: 3
+	},
+	{
+	  name: '低音单簧管',
+	  code: 'Bass Clarinet',
+	  id: 4
+	},
+	{
+	  name: '中音萨克斯',
+	  code: 'Alto Saxophone',
+	  id: 5
+	},
+	{
+	  name: '次中音萨克斯',
+	  code: 'Tenor Saxophone',
+	  id: 6
+	},
+	{
+	  name: '高音萨克斯',
+	  code: 'Soprano Saxophone',
+	  id: 7
+	},
+	{
+	  name: '上低音萨克斯',
+	  code: 'Baritone Saxophone',
+	  id: 8
+	},
+	{
+	  name: '双簧管',
+	  code: 'Oboe',
+	  id: 9
+	},
+	{
+	  name: '大管',
+	  code: 'Bassoon',
+	  id: 10
+	},
+	{
+	  name: '小号',
+	  code: 'Trumpet',
+	  id: 11
+	},
+	{
+	  name: '圆号',
+	  code: 'Horn',
+	  id: 12
+	},
+	{
+	  name: '长号',
+	  code: 'Trombone',
+	  id: 13
+	},
+	{
+	  name: '上低音号',
+	  code: 'Baritone',
+	  id: 14
+	},
+	{
+	  name: '次中音号',
+	  code: 'Euphonium',
+	  id: 15
+	},
+	{
+	  name: '大号',
+	  code: 'Tuba',
+	  id: 16
+	},
+	{
+	  name: '钢琴',
+	  code: 'Piano',
+	  id: 17
+	},
+	{
+	  name: '电钢琴',
+	  code: 'Electronical Piano',
+	  id: 18
+	},
+	{
+	  name: '钢片琴',
+	  code: 'Glockenspiel',
+	  id: 19
+	},
+	{
+	  name: '小提琴',
+	  code: 'Violin',
+	  id: 20
+	},
+	{
+	  name: '中提琴',
+	  code: 'Viola',
+	  id: 21
+	},
+	{
+	  name: '大提琴',
+	  code: 'Violoncello',
+	  id: 22
+	},
+	{
+	  name: '低音提琴',
+	  code: 'Contrabass',
+	  id: 23
+	},
+	{
+	  name: '架子鼓',
+	  code: 'Drum Set',
+	  id: 24
+	},
+	{
+	  name: '小鼓',
+	  code: 'Snare Drum',
+	  id: 25
+	},
+	{
+	  name: '马林巴',
+	  code: 'Marimba',
+	  id: 26
+	},
+	{
+	  name: '颤音琴',
+	  code: 'Vibraphone',
+	  id: 27
+	},
+	{
+	  name: '钟琴',
+	  code: 'Chimes',
+	  id: 28
+	},
+	{
+	  name: '木琴',
+	  code: 'Xylophone',
+	  id: 29
+	},
+	{
+	  name: '管钟',
+	  code: 'Tubular Bells',
+	  id: 30
+	},
+	{
+	  name: '定音鼓',
+	  code: 'Timpani',
+	  id: 31
+	},
+	{
+	  name: '键盘',
+	  code: 'Mallets',
+	  id: 32
+	},
+	{
+	  name: '排箫',
+	  code: 'Panpipes',
+	  id: 33
+	},
+	{
+	  name: '陶笛',
+	  code: 'Ocarina',
+	  id: 34
+	},
+	{
+	  name: '陶笛',
+	  code: 'Alto Ocarina',
+	  id: 34
+	},
+	{
+	  name: '葫芦丝',
+	  code: 'Woodwind',
+	  id: 35
+	},
+	{
+	  name: '葫芦丝',
+	  code: 'Hulusi',
+	  id: 35
+	},
+	{
+	  name: '口风琴',
+	  code: 'Nai',
+	  id: 36
+	},
+	{
+	  name: '口风琴',
+	  code: 'Melodica',
+	  id: 36
+	},
+	{
+	  name: '德式竖笛',
+	  code: 'Tenor Recorder',
+	  id: 37
+	},
+	{
+	  name: '德式竖笛',
+	  code: 'German Recorder',
+	  id: 37
+	},
+	{
+	  name: '英式竖笛',
+	  code: 'Baroque Recorder',
+	  id: 38
+	},
+	{
+	  name: '高音陶笛',
+	  code: 'Whistling',
+	  id: 39
+	},
+	{
+	  name: '高音陶笛',
+	  code: 'Soprano Ocarina',
+	  id: 39
+	},
+]
+
+export let instruments: any = {}
+
 /** 获取分轨名称 */
 export const getInstrumentName = (name = '') => {
-  name = name.toLocaleLowerCase().replace(/ /g, '')
-  if (!name) return ''
-  for(let key in instruments){
-    const _key = key.toLocaleLowerCase().replace(/ /g, '')
-    if (_key.includes(name)){
-      return instruments[key]
-    }
-  }
-  for(let key in instruments){
-    const _key = key.toLocaleLowerCase().replace(/ /g, '')
-    if (name.includes(_key)){
-      return instruments[key]
-    }
-  }
-  return ''
-};
+	name = name.toLocaleLowerCase().replace(/ /g, '')
+	if (!name) return ''
+	// 全匹配声轨名称
+	for(let key in instruments){
+	  const _key = key.toLocaleLowerCase().replace(/ /g, '')
+	  if (_key === name){
+		return instruments[key]
+	  }
+	}
+	// 用返回的code模糊匹配传入的xml声轨名称name
+	for(let key in instruments){
+	  const _key = key.toLocaleLowerCase().replace(/ /g, '')
+	  if (name.includes(_key)){
+		return instruments[key]
+	  }
+	}
+  //   for(let key in instruments){
+  //     const _key = key.toLocaleLowerCase().replace(/ /g, '')
+  //     if (name.includes(_key)){
+  //       return instruments[key]
+  //     }
+  //   }
+	return ''
+  };
 
 /**
  * 乐器排序
@@ -275,4 +512,41 @@ export const sortMusical = (name: string, index: number) => {
 		break;
 	}
 	return sortId
-  }
+  }
+
+export const fixInstrumentNameCode = (trackId: string | number) => {
+	let code: any;
+	const trackName = instruments[trackId] || ''
+	if (trackName.includes('长笛')) {
+		code = 2
+	} else if (trackName.includes('单簧管')) {
+		code = 4
+	} else if (trackName.includes('萨克斯')) {
+		code = 5
+	} else if (trackName.includes('小号')) {
+		code = 12
+	} else if (trackName.includes('圆号')) {
+		code = 13
+	} else if (trackName.includes('长号')) {
+		code = 14
+	} else if (trackName.includes('上低音号')) {
+		code = 15
+	} else if (trackName.includes('大号')) {
+		code = 17
+	} else if (trackName.includes('德式竖笛')) {
+		code = 'piccolo'
+	} else if (trackName.includes('英式竖笛')) {
+		code = 'baroque-recorder'
+	} else if (trackName.includes('葫芦丝')) {
+		code = 'hulusi-flute'
+	} else if (trackName.includes('排箫')) {
+		code = 'pan-flute'
+	} else if (trackName.includes('高音陶笛')) {
+		code = 'whistling'
+	} else if (trackName.includes('陶笛')) {
+		code = 'ocarina'
+	} else if (trackName.includes('口风琴')) {
+		code = 'melodica'
+	}
+	return code;
+}

+ 1 - 1
src/constant/instrumentsClassfiy.ts

@@ -3,7 +3,7 @@ const instrumentsClassfiy: any = {
 	"4": ["Clarinet"],
 	"6": ["Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", "Soprano Saxophone", "Alto Saxophone", "Tenor Saxophone", "Baritone Saxophone"],
 	"12": ["Trumpet", "Muted Trumpet"],
-	"13": ["Horn in F", "French Horn"],
+	"13": ["Horn in F", "French Horn", "Horns in F"],
 	"14": ["Trombone"],
 	"15": ["Euphonium"],
 	"17": ["Tuba"],

+ 8 - 0
src/helpers/calcSpeed.ts

@@ -43,6 +43,12 @@ export const speedInfo: { [key in string]: number } = {
 	faster: 1.333333333,
 	"molto allargando": 1.333333333,
 	stringendo: 0.8,
+	"poco a poco rit.": 1.333333333,
+	"rit. poco a poco": 1.333333333,
+	"Ritardando": 1.333333333,
+	"Ritenuto": 1.333333333,
+	"accelerate": 0.8,
+	"poco a poco accel.": 0.8,
 };
 
 /**
@@ -117,7 +123,9 @@ export type GradualItem = {
  */
 export const getGradualLengthByXml = (xml: string) => {
 	const firstPartXml = onlyVisible(xml, 0, 'calc')
+	//console.time('解析xml 耗时2')
 	const xmlParse = new DOMParser().parseFromString(firstPartXml, "text/xml");
+	//console.timeEnd('解析xml 耗时2')
 	const measures = Array.from(xmlParse.querySelectorAll("measure"));
 	const notes = Array.from(xmlParse.querySelectorAll("note"));
 	const words = Array.from(xmlParse.querySelectorAll("words"));

+ 9 - 1
src/helpers/communication.ts

@@ -557,4 +557,12 @@ export const simple_musicPage = (content: any) => {
 /** 监听重新评测消息 */
 export const api_retryEvaluating = (callback: any) => {
 	listenerMessage("retryEvaluating", callback);
-}
+}
+
+/** 通知app上传评测音频 */
+export const api_recordAudioUpload = (content: any) => {
+	postMessage({
+		api: "recordAudioUpload",
+		content,
+	});
+};

+ 0 - 25
src/helpers/customMusicScore.ts

@@ -1073,34 +1073,9 @@ export const setGlobalMusicSheet = () => {
 	}
 }
 
-/** 设置自定义渐慢 */
-export const setCustomGradual = () => {
-	if (state.gradualTimes) {
-		const detailId = state.cbsExamSongId + "";
-		const partIndex = state.partIndex + "";
-		if (["12280"].includes(detailId) && ["24"].includes(partIndex)) {
-			state.gradualTimes["8"] = "00:26:10";
-			state.gradualTimes["66"] = "01:53:35";
-			state.gradualTimes["90"] = "02:41:40";
-		}
-	}
-};
-
 /** 设置自定义音符数据 */
 export const setCustomNoteRealValue = () => {
 	const detailId = state.cbsExamSongId + "";
-    const partIndex = state.partIndex + "";
-	if (["2670"].includes(detailId)) {
-		customData.customNoteRealValue = {
-			0: 0.03125,
-		};
-	}
-	if (["12673"].includes(detailId) && ['22'].includes(partIndex)) {
-		customData.customNoteRealValue = {
-			208: 0.125,
-		};
-	}
-
     if (["12667", "12673"].includes(detailId)){
         customData.customNoteCurrentTime = true
     }

+ 227 - 31
src/helpers/formateMusic.ts

@@ -15,6 +15,7 @@ import {
 } from "/osmd-extended/src";
 import { GradualChange, speedInfo } from "./calcSpeed";
 import { beatUnitTo, speedBeatTo } from "/src/helpers/beatConfig"
+import { xmlDocRef } from "/src/view/music-score"
 
 const browserInfo = browser();
 dayjs.extend(duration);
@@ -246,13 +247,16 @@ export type CustomInfo = {
 };
 
 /** 从xml中获取自定义信息,并删除多余的字符串 */
-export const getCustomInfo = (xml: string): CustomInfo => {
+export const getCustomInfo = (xml: string, resourceType?: string): CustomInfo => {
 	const data = {
 		showSpeed: true,
 		parsedXML: xml,
 	};
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
-	const words: any = xmlParse.getElementsByTagName("words");
+	//console.time('解析xml 耗时3')
+	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const xmlParse = xmlDocRef.value && resourceType === 'init' ? xmlDocRef.value : new DOMParser().parseFromString(xml, "text/xml");
+	//console.timeEnd('解析xml 耗时3')
+	const words: any = xmlParse?.getElementsByTagName("words");
 	for (const word of words) {
 		if (word && word.textContent?.trim() === "隐藏速度") {
 			data.showSpeed = false;
@@ -361,7 +365,10 @@ export const onlyVisible = (xml: string, partIndex: number, resourceType?: strin
 	if (!xml) return "";
 	// console.log('原始xml')
 	const detailId = state.examSongId + "";
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	//console.time('解析xml 耗时4')
+	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const xmlParse = xmlDocRef.value && !resourceType ? xmlDocRef.value : new DOMParser().parseFromString(xml, "text/xml");
+	//console.timeEnd('解析xml 耗时4')
 	const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
 	const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0]?.textContent?.trim() || "");
 	const parts: any = xmlParse.getElementsByTagName("part");
@@ -527,20 +534,188 @@ export const onlyVisible = (xml: string, partIndex: number, resourceType?: strin
 	return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse));
 };
 
-export const onlyVisible2 = (xml: string): string => {
+export const onlyVisible2 = (xml: string, partIndexs: Array<any>, resourceType?: string): string => {
 	if (!xml) return "";
 	// console.log('原始xml')
-	//const detailId = state.examSongId + "";
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const detailId = state.examSongId + "";
+	//console.time('解析xml 耗时4')
+	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const xmlParse = xmlDocRef.value ? xmlDocRef.value : new DOMParser().parseFromString(xml, "text/xml");
+	//console.timeEnd('解析xml 耗时4')
 	const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
-	//const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0]?.textContent?.trim() || "");
-	//state.partListNames = partListNames;
-	Array.from(partList).forEach((part) => {
-		let partListName = part.getElementsByTagName("part-name")?.[0]?.textContent?.trim();
-		if (!state.canSelectTracks.includes(partListName)) {
-			part.parentNode?.removeChild(part);
+	const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0]?.textContent?.trim() || "");
+	const parts: any = xmlParse.getElementsByTagName("part");
+	// const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
+	const hasMeasureIdx = Array.from(parts).findIndex((item: any) => item.getElementsByTagName("measure").length) || 0;
+	const firstMeasures = [...parts[hasMeasureIdx]?.getElementsByTagName("measure")];
+	const metronomes = [...parts[0]?.getElementsByTagName("metronome")];
+	const words = [...parts[0]?.getElementsByTagName("words")];
+	const codas = [...parts[0]?.getElementsByTagName("coda")];
+	const rehearsals = [...parts[0]?.getElementsByTagName("rehearsal")];
+
+	/** 第一分谱如果是约定的配置分谱则跳过 */
+	if (partListNames[0]?.toLocaleUpperCase?.() === "COMMON") {
+		partIndexs = partIndexs.map(item => item + 1)
+		//partListNames.shift();
+	}
+	const visiblePartInfo = partList[partIndexs[0]];
+	let ids: any = [];
+	partIndexs.forEach(item => {
+		if (partList[item]) {
+			ids.push(partList[item]?.getAttribute("id"))
 		}
-	});
+	})
+	// console.log(visiblePartInfo, partIndex)
+	// 根据后台已选择的分轨筛选出能切换的声轨
+	//state.partListNames = partListNames;
+	// console.log('分轨名称',state.partListNames)
+	if (visiblePartInfo && ids.length) {
+		const id = visiblePartInfo.getAttribute("id");
+		Array.from(parts).forEach((part: any) => {
+			if (part && !ids.includes(part.getAttribute("id")) ) {
+				part.parentNode?.removeChild(part);
+				// 不等于第一行才添加避免重复添加
+			} else if (part && part.getAttribute("id") !== "P1") {
+				if (part && part.getAttribute("id") === id) {
+					// 速度标记仅保留最后一个
+					const metronomeData: {
+						[key in string]: Element;
+					} = {};
+					for (let i = 0; i < metronomes.length; i++) {
+						const metronome = metronomes[i];
+						const metronomeContainer = metronome.parentElement?.parentElement?.parentElement;
+						if (metronomeContainer) {
+							const index = firstMeasures.indexOf(metronomeContainer);
+							metronomeData[index] = metronome;
+						}
+					}
+					Object.values(metronomeData).forEach((metronome) => {
+						const metronomeContainer: any = metronome.parentElement?.parentElement;
+						const parentMeasure: any = metronomeContainer?.parentElement;
+						const measureMetronomes = [...(parentMeasure?.childNodes || [])];
+						const metronomesIndex = metronomeContainer ? measureMetronomes.indexOf(metronomeContainer) : -1;
+						// console.log(parentMeasure)
+						if (parentMeasure && metronomesIndex > -1) {
+							const index = firstMeasures.indexOf(parentMeasure);
+							const activeMeasure = part.getElementsByTagName("measure")[index];
+							setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure);
+						}
+					});
+					/** word比较特殊需要精确到note位置 */
+					words.forEach((word) => {
+						let text = word.textContent || "";
+						text = ["cresc."].includes(text) ? "" : text;
+						if ((isSpecialMark(text) || isSpeedKeyword(text) || isGradientWords(text) || isRepeatWord(text) || GRADIENT_SPEED_RESET_TAG) && text) {
+							const wordContainer = word.parentElement?.parentElement;
+							const parentMeasure = wordContainer?.parentElement;
+							const measureWords = [...(parentMeasure?.childNodes || [])];
+							const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1;
+							if (wordContainer && parentMeasure && wordIndex > -1) {
+								const index = firstMeasures.indexOf(parentMeasure);
+								const activeMeasure = part.getElementsByTagName("measure")[index];
+								// 找当前小节是否包含word标签
+								const _words = Array.from(activeMeasure?.getElementsByTagName("words") || []);
+								// 遍历word标签,检查是否和第一小节重复,如果有重复则不平移word
+								const total = _words.reduce((total: any, _word: any) => {
+									if (_word.textContent?.includes(text)) {
+										total++;
+									}
+									return total;
+								}, 0);
+								if (total === 0) {
+									if (["12280"].includes(detailId)) {
+										activeMeasure?.insertBefore(wordContainer.cloneNode(true), activeMeasure?.childNodes[wordIndex]);
+									} else {
+										setElementNoteBefore(wordContainer, parentMeasure, activeMeasure);
+									}
+								}
+							}
+						}
+					});
+					/** word比较特殊需要精确到note位置 */
+					codas.forEach((coda) => {
+						const wordContainer = coda.parentElement?.parentElement;
+						const parentMeasure = wordContainer?.parentElement;
+						const measureWords = [...(parentMeasure?.childNodes || [])];
+						const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1;
+						if (wordContainer && parentMeasure && wordIndex > -1) {
+							const index = firstMeasures.indexOf(parentMeasure);
+							const activeMeasure = part.getElementsByTagName("measure")[index];
+							if (["12280"].includes(detailId)) {
+								activeMeasure?.insertBefore(wordContainer.cloneNode(true), activeMeasure?.childNodes[wordIndex]);
+							} else {
+								setElementNoteBefore(wordContainer, parentMeasure, activeMeasure);
+							}
+						}
+					});
+					rehearsals.forEach((rehearsal) => {
+						const container = rehearsal.parentElement?.parentElement;
+						const parentMeasure = container?.parentElement;
+						// console.log(rehearsal)
+						if (parentMeasure) {
+							const index = firstMeasures.indexOf(parentMeasure);
+							part.getElementsByTagName("measure")[index]?.appendChild(container.cloneNode(true));
+							// console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
+						}
+					});
+				}
+
+			} else {
+				if (part && part.getAttribute("id") === id) {
+					words.forEach((word, idx) => {
+						const text = word.textContent || "";
+						// if (idx == 0 && text) {
+						// 	word.textContent = '测试一下'
+						// 	word.setAttribute('default-y',60)
+						// 	word.setAttribute('margin-left',300)
+						// 	word.setAttribute('y',300)
+						// 	word.outerHTML = '<words default-x="155" default-y="100" justify="right" valign="middle" font-family="SimHei" font-style="normal" font-size="11.9365" font-weight="normal">哈哈哈哈哈</words>'
+						// }
+						if (isSpeedKeyword(text) && text) {
+							const wordContainer = word.parentElement?.parentElement?.parentElement;
+							if (wordContainer && wordContainer.firstElementChild && wordContainer.firstElementChild !== word) {
+								const wordParent = word.parentElement?.parentElement;
+								const fisrt = wordContainer.firstElementChild;
+								wordContainer.insertBefore(wordParent, fisrt);
+							}
+						}
+					});
+				}
+
+			}
+
+			// 最后一个小节的结束线元素不在最后 调整
+			if (part && part.getAttribute("id") === id) {
+				if (!resourceType) {
+					const backups = Array.from(part.getElementsByTagName('backup')) || []
+					for (let backup of backups) {
+						// @ts-ignore
+						if (backup && backup?.getElementsByTagName('duration')?.length) {
+							state.isSingleMutliTrack = true;
+							break;
+						}
+					}
+				}
+				const barlines = part.getElementsByTagName("barline");
+				const lastParent = barlines[barlines.length - 1]?.parentElement;
+				if (lastParent?.lastElementChild?.tagName !== "barline") {
+					const children = lastParent?.children || [];
+					for (let el of children) {
+						if (el.tagName === "barline") {
+							// 将结束线元素放到最后
+							lastParent?.appendChild(el);
+							break;
+						}
+					}
+				}
+			}
+		});
+		Array.from(partList).forEach((part) => {
+			if (part && !ids.includes(part.getAttribute("id")) ) {
+				part.parentNode?.removeChild(part);
+			}
+		});
+	}
 	// console.log(xmlParse)
 	return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse));
 };
@@ -624,7 +799,9 @@ export const formatZoom = (num = 1) => {
 /** 妙极客多分轨的曲子,可能没有part-name标签,需要手动加上该标签 */
 export const xmlAddPartName = (xml: string) => {
 	if (!xml) return "";
+	console.time('解析xml 耗时')
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	console.timeEnd('解析xml 耗时')
 	const scoreParts = Array.from(xmlParse.getElementsByTagName("score-part"));
 	for (const scorePart of scoreParts) {
 		if (scorePart.getElementsByTagName("part-name").length === 0) {
@@ -638,16 +815,19 @@ export const xmlAddPartName = (xml: string) => {
 			scorePart.getElementsByTagName("part-name")[0].textContent = scorePart.getAttribute("id") || "";
 		}
 	}
+	xmlDocRef.value = xmlParse;
 	return new XMLSerializer().serializeToString(xmlParse);
 }
 
 /** 格式化曲谱
  * 1.全休止符的小节,没有音符默认加个全休止符
  */
-export const formatXML = (xml: string, xmlUrl?: string): string => {
+export const formatXML = (xml: string, xmlUrl?: string, resourceType?: string): string => {
 	if (!xml) return "";
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
-
+	//console.time('解析xml 耗时7')
+	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const xmlParse = xmlDocRef.value && resourceType === 'init' ? xmlDocRef.value : new DOMParser().parseFromString(xml, "text/xml");
+	//console.timeEnd('解析xml 耗时7')
 	// 声调
 	const fifths = xmlParse.getElementsByTagName("fifths");
 	if (fifths && fifths.length) {
@@ -737,6 +917,7 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 	// 前面小节的拍子
 	let preBeats: number = 4;
 	let preBeatType: number = 4;
+	let baseDivisions: number = 256;
 	// 小节中如果没有节点默认为休止符
 	for (const measure of measures) {
 		if (beats === -1 && measure.getElementsByTagName("beats").length) {
@@ -753,7 +934,8 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 		const currentBeatType = measure.getElementsByTagName("beat-type").length ? measure.getElementsByTagName("beat-type")[0]?.textContent : preBeatType;
 		preBeats = Number(currentBeats);
 		preBeatType = Number(currentBeatType);
-		const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || "256");
+		const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || String(baseDivisions));
+		baseDivisions = divisions
 		// 如果note节点里面有space节点,并且没有duration节点,代表这是一个空白节点,需要删除
 		if (measure.getElementsByTagName("note").length && state.isEvxml) {
 			const noteList = Array.from(measure.getElementsByTagName("note")) || [];
@@ -803,6 +985,7 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 			transferJianNote(measure, divisions, preBeats, preBeatType)
 		}
 	}
+	xmlDocRef.value = xmlParse;
 	return new XMLSerializer().serializeToString(xmlParse);
 };
 
@@ -885,9 +1068,16 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	let differFrom = 0;
 	// let testIdx = 0;
 	let repeatIdx = 0; // 循环的次数
-	// 当多选声部的时候 ,取选择的第一个声部
-	const firstTrackName = state.combinePartIndexs.length>1 ? state.partListNames[state.combinePartIndexs[0]] : state.canSelectTracks[0] || "";
-	const currentTrackIndex = state.isCombineRender && state.combinePartIndexs.length > 1 ? state.combinePartIndexs[0] : 0;
+	/**
+	 * 当多选声部的时候 ,取选择的第一个声部
+	 * 总谱渲染时,需要取第一个渲染的声轨名字,canSelectTracks返回的声轨名字可能不是第一个,顺序有问题,需要用state.osmd.Sheet.Instruments
+	 */
+	// const firstTrackName = state.combinePartIndexs.length>1 ? state.partListNames[state.combinePartIndexs[0]] : state.canSelectTracks[0] || "";
+	const filterInstruments = state.osmd.Sheet.Instruments.filter(item => item.Name?.toLocaleLowerCase() !== 'common' )
+	const firstTrackName = state.combinePartIndexs.length>1 ? state.partListNames[state.combinePartIndexs[0]] : (filterInstruments[0].Name || filterInstruments[0].NameLabel.text || "");
+	// const currentTrackIndex = state.isCombineRender && state.combinePartIndexs.length > 1 ? state.combinePartIndexs[0] : 0;
+	const currentTrackIndex = 0;
+
 	while (!iterator.EndReached) {
 		// console.log({ ...iterator });
 		/** 多声轨合并显示,当前音符的时值取所有声轨中的最小值 */
@@ -946,7 +1136,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				notes.push(...n.Notes);
 				return notes;
 			}, [] as any);
-			voiceNotes = voiceNotes.filter((note: any) => !note.IsGraceNote)
+			// 过滤掉倚音和和弦音符
+			voiceNotes = voiceNotes.filter((note: any) => (!note.IsGraceNote && !note.IsChordNote) )
 			voiceNotes = voiceNotes.sort((a: any, b: any) => a?.length?.realValue - b?.length?.realValue);
 			currentTime = voiceNotes?.[0]?.length?.realValue || 0;
 
@@ -1255,14 +1446,18 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				 * 管乐迷,部分弱起的曲目,mp3制作不标准,没有按照补齐弱起后的时间进行制作,需要单独处理
 				 * 2670
 				*/
-				if (["2670"].includes(state.cbsExamSongId)) {
-					// fixtime -= _firstMeasureRealValue * formatBeatUnit(beatUnit) * (60 / beatSpeed);
-				} else {
-					if (difftime > 0 && !state.isEvxml) {
-						fixtime += difftime;
-						state.fixtime = fixtime;
-					}
-				}
+				// if (["2670"].includes(state.cbsExamSongId)) {
+				// 	// fixtime -= _firstMeasureRealValue * formatBeatUnit(beatUnit) * (60 / beatSpeed);
+				// } else {
+				// 	if (difftime > 0 && !state.isEvxml) {
+				// 		fixtime += difftime;
+				// 		state.fixtime = fixtime;
+				// 	}
+				// }
+				if (difftime > 0 && !state.isEvxml) {
+					fixtime += difftime;
+					state.fixtime = fixtime;
+				}				
 				// 管乐迷 diff获取不准确时, 弱起补齐
 				if (["2589", "2561", "2560", "2559", "2558", "2556", "2555", "2554"].includes(detailId)) {
 					// difftime = iterator.currentTimeStamp.realValue * formatBeatUnit(beatUnit) * (60 / beatSpeed);
@@ -1384,7 +1579,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				trackIndex: note.trackIndex, // 当前的音符属于第几条分轨
 				isStaccato: note.voiceEntry.isStaccato(),
 				isRestFlag: note.isRestFlag,
-				noteId: note.NoteToGraphicalNoteObjectId,
+				noteId: note.NoteToGraphicalNoteObjectId === undefined ? `restNote${note.sourceMeasure.MeasureNumberXML}` : note.NoteToGraphicalNoteObjectId,
+				// noteId: note.NoteToGraphicalNoteObjectId,
 				measureListIndex: note.sourceMeasure.measureListIndex,
 				MeasureNumberXML: note.sourceMeasure.MeasureNumberXML, // 当前的小节数,(从1开始)
 				_noteLength: _noteLength,

+ 60 - 0
src/hooks/errorLog/index.ts

@@ -0,0 +1,60 @@
+import { uploadErrorLog } from "./uploadLog";
+import state from "/src/state";
+import { getQuery } from "/src/utils/queryString";
+
+type uploadType = {
+  clientType?: string;
+  phone?: string | undefined | null;
+  userAgent?: string;
+  appType?: string;
+  content?: string;
+  exceptionType?: string;
+  exceptionTime?: string;
+  deviceType?: string | null;
+  deviceVersion?: string | null
+}
+
+const query: any = getQuery();
+
+/**
+ * 页面有报错时上传错误日志
+ * @params
+ */
+export default function useErrorLog() {
+  const _uploadErrorLog = async (event: any) => {
+    // 错误信息
+    const contentError = `Error message: ${event.target.tagName || ''};${
+      event.target.src || event.target.href || ''
+    };lineno: ${event.lineno || ''};colno: ${event.colno || ''};message: ${
+      event.message || ''
+    };filename: ${event.filename || ''};fileUrl: ${
+      window.location.href
+    };reason: ${event.reason?.message || ''};
+    stack: ${event.reason?.stack || ''};
+    bizId: ${state.examSongId || query.id || ''};
+    partIndex: ${query["part-index"] || state.partIndex || 0}
+    partName: ${decodeURIComponent(query["part-name"] || '') || ''};`;
+    uploadErrorLog(contentError)
+  };
+  /**
+   * 开始监听错误日志并上传
+   */
+  const startListenErrorLog = () => {
+    console.log('mount useErrorLog');
+    window.addEventListener('error', _uploadErrorLog);
+    window.addEventListener('unhandledrejection', _uploadErrorLog);
+  };
+
+  /**
+   * 停止监听
+   */
+  const stopListenErrorLog = () => {
+    window.removeEventListener('error', _uploadErrorLog);
+    window.removeEventListener('unhandledrejection', _uploadErrorLog);
+  };
+
+  return {
+    startListenErrorLog,
+    stopListenErrorLog
+  };
+}

+ 42 - 0
src/hooks/errorLog/uploadLog.ts

@@ -0,0 +1,42 @@
+import state from "/src/state";
+import dayjs from 'dayjs';
+import { sysExceptionLogSave } from '/src/utils/baseApi'
+import { browser } from "/src/utils";
+import { storeData } from "/src/store";
+
+// 上传错误日志
+export const uploadErrorLog = async (contentError: string) => {
+	//
+    let defaultParams = {
+		appKey: 'GYM', // 应用标识(GYT,GYM,KT,KLX,CBS),可用值:GYM,GYT,KLX,KLX_JG,KT,CBS
+		appType: browser().android ? 'ANDROID' : browser().ios && storeData.isApp ? 'IOS' : 'WEB', // 应用类型(IOS,ANDROID,HARMONY),可用值:IOS,ANDROID,HARMONY,WEB
+		clientType: '', // 客户端类型(TEACHER,STUDENT,SCHOOL,BACKEND),可用值:BACKEND,SCHOOL,TEACHER,STUDENT,TENANT	
+		content: '', // 内容
+		deviceType: null, // 设备类型
+		deviceVersion: null, // 设备版本
+		exceptionTime: null, // 异常时间
+		exceptionType: 'ERROR', // 异常类型(ERROR,RECORD),可用值:ERROR,RECORD	
+		phone: null, // 手机号
+		userAgent: window.navigator.userAgent, // 客户端信息
+		
+	  }
+	console.log('errorLog','错误',event)
+	try {
+	console.log(window.location.hash, 'errorLog')
+
+	const params = [
+		{
+		...defaultParams,
+		clientType: state.systemType === 'teacher' ? 'TEACHER' : state.systemType === 'student' ? 'STUDENT' : 'BACKEND',
+		content: contentError,
+		exceptionTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+		phone: storeData.user?.phone,
+		userAgent: window.navigator.userAgent,
+		}
+	];
+	// console.log(params, '错误日志参数', 'errorLog')
+	await sysExceptionLogSave(params);
+	} catch {
+	//
+	}
+}

+ 51 - 0
src/page-instrument/App.tsx

@@ -8,6 +8,7 @@ import { studentQueryUserInfo } from "./api";
 import { api_cloudLoading, api_getToken } from "../helpers/communication";
 import { showToast } from "vant";
 import state from "/src/state"
+import { values } from "lodash";
 
 export default defineComponent({
   name: "App",
@@ -125,6 +126,56 @@ export default defineComponent({
       //     event.preventDefault();
       //   }
       // });
+
+      /**
+       * DNS 解析耗时: domainLookupEnd - domainLookupStart
+       * TCP 连接耗时: connectEnd - connectStart
+       * SSL 安全连接耗时: connectEnd - secureConnectionStart
+       * 网络请求耗时 (TTFB): responseStart - requestStart
+       *数据传输耗时: responseEnd - responseStart
+        *DOM 解析耗时: domInteractive - responseEnd
+        *资源加载耗时: loadEventStart - domContentLoadedEventEnd
+        *First Byte时间: responseStart - domainLookupStart
+        *白屏时间: responseEnd - fetchStart
+        *首次可交互时间: domInteractive - fetchStart
+        *DOM Ready 时间: domContentLoadEventEnd - fetchStart
+        *页面完全加载时间: loadEventStart - fetchStart
+        *http 头部大小: transferSize - encodedBodySize
+        *重定向次数:performance.navigation.redirectCount
+        *重定向耗时: redirectEnd - redirectStart
+       */
+      window.onload = function() {
+        console.log('加载完成')
+        let timing: any = performance.getEntriesByType('navigation')[0] || {};
+        const { domainLookupEnd, domainLookupStart, connectEnd, connectStart, responseStart,
+          requestStart, responseEnd, domInteractive, loadEventStart, domContentLoadedEventEnd,
+          fetchStart, secureConnectionStart, transferSize, encodedBodySize,
+          redirectEnd, redirectStart
+        } = timing
+        //console.log(timing.domInteractive);
+        //console.log(timing.fetchStart);
+        let diff = timing.domInteractive - timing.fetchStart;
+        //console.log("TTI: " + diff);
+        const timeInfo = [
+          { '类型': 'DNS 解析耗时', '耗时': domainLookupEnd - domainLookupStart},
+          { '类型': 'TCP 连接耗时', '耗时': connectEnd - connectStart},
+          { '类型': 'SSL 安全连接耗时', '耗时': connectEnd - secureConnectionStart},
+          { '类型': '网络请求耗时', '耗时': responseStart - requestStart},
+          { '类型': '数据传输耗时', '耗时': responseEnd - responseStart},
+          { '类型': 'DOM 解析耗时', '耗时': domInteractive - responseEnd},
+          { '类型': '资源加载耗时', '耗时': loadEventStart - domContentLoadedEventEnd},
+          { '类型': 'First Byte时间', '耗时': responseStart - domainLookupStart},
+          { '类型': '白屏时间', '耗时': responseEnd - fetchStart},
+          { '类型': '首次可交互时间', '耗时': domInteractive - fetchStart},
+          { '类型': 'DOM Ready 时间', '耗时': domContentLoadedEventEnd - fetchStart},
+          { '类型': '页面完全加载时间', '耗时': loadEventStart - fetchStart},
+          { '类型': 'http 头部大小', '耗时': transferSize - encodedBodySize},
+          { '类型': '重定向次数', '耗时': performance.navigation.redirectCount},
+          { '类型': '重定向耗时', '耗时': redirectEnd - redirectStart},
+        ];
+        console.table(timeInfo);
+        
+      };
     });
 
     onUnmounted(() => {

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

@@ -83,6 +83,7 @@ export default defineComponent({
       if (item.id === state.examSongId) return;
       // 暂停播放
       togglePlay("paused");
+      state.evaluatAudioInitDone = false
       postMessage({
         api: "cloudLoading",
         content: {

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

@@ -1346,13 +1346,18 @@ export const EvaluatingReportDriver = defineComponent({
             title: "",
             description: "",
             popoverClass: "popoverClass popoverClassReport3 popoverClose",
-            align: "start",
+            align: "end",
             side: "bottom",
             prevBtnText: "再看一遍",
             doneBtnText: "完成",
             showButtons: ["next", "previous"],
             onPopoverRender: (popover: PopoverDOM, options: { config: Config; state: State }) => {
-              driverInitialPosition(popover, options);
+              options.config.stageRadius = 8;
+              options.config.stagePadding = 5;
+              try {
+                const rect = options.state.activeElement?.getBoundingClientRect();
+                popover.wrapper.style.marginLeft = ((rect?.width || 0) / 2) * -1 + 4 + "px";
+              } catch {}
             },
             onPrevClick: () => {
               driverObj.drive(0);

+ 31 - 10
src/page-instrument/evaluat-model/evaluat-result/index.tsx

@@ -1,5 +1,5 @@
 import { defineComponent, onMounted, reactive, watch, computed } from "vue";
-import { Popover } from "vant";
+import { Popover, showToast } from "vant";
 import styles from "./index.module.less";
 import state from "/src/state";
 import icon1 from "../icons/1.png";
@@ -23,6 +23,8 @@ import { api_musicPracticeRecordSave } from "../../api";
 import { getAudioDuration } from "/src/view/audio-list";
 import { debounce } from "/src/utils";
 import { EvaluatingResultDriver } from "../../custom-plugins/guide-driver";
+import { api_recordAudioUpload } from "/src/helpers/communication";
+import { uploadErrorLog } from '/src/hooks/errorLog/uploadLog'
 
 export default defineComponent({
   name: "evaluatResult",
@@ -73,10 +75,26 @@ export default defineComponent({
         body.lessonDetailId = query.evaluatingRecord
       }
       data.saveLoading = true;
-      const res = await api_musicPracticeRecordSave(body);
-      if (res?.code === 200) {
-        evaluatingData.resultData.recordId = res.data;
+      try {
+        const res = await api_musicPracticeRecordSave(body);
+        if (res?.code === 200) {
+          evaluatingData.resultData.recordId = res.data;
+          // 如果评测结果没有返回音频,需要调用api通知APP端上传音频
+          if (!evaluatingData.resultData.url && evaluatingData.resultData.recordId) {
+            api_recordAudioUpload({
+              recordId: evaluatingData.resultData.recordId
+            })
+          }
+        }
+      } catch (err:any) {
+        const contentError = `reason: ${err?.message || ''};stack: ${err?.stack || ''};bizId: ${state.examSongId || query.id || ''};partIndex: ${query["part-index"] || state.partIndex || 0};partName: ${decodeURIComponent(query["part-name"] || '') || ''};`;
+        uploadErrorLog(contentError)
+        evaluatingData.resulstMode = false
+        showToast({
+          message: "评测信息保存失败",
+        });
       }
+      state.isLoading = false
       evaluatingData.needReplayEvaluat = evaluatingData.oneselfCancleEvaluating ? true : false;
       data.saveLoading = false;
     };
@@ -98,9 +116,9 @@ export default defineComponent({
 
     const isHuaWeiPad = navigator?.userAgent?.includes("UAWEIVRD-W09") ? true : false
     onMounted(() => {
-      if (!evaluatingData.isErrorState) {
-        handleAddRecord();
-      }
+      // if (!evaluatingData.isErrorState) {
+      //   handleAddRecord();
+      // }
       // console.log('评测等级',evaluatingData.resultData.leve)
     });
 
@@ -115,11 +133,14 @@ export default defineComponent({
             }
           }, 0);
         }
+      },
+      {
+        immediate: true
       }
     );
     return () => (
       <>
-        {!evaluatingData.hideResultModal && (
+        {!evaluatingData.hideResultModal && evaluatingData.resultData.recordId && (
           <div class={styles.evaluatResult}>
             <div class={styles.closeBtn} onClick={() => emit("close")}>
               <img src={iconBack} />
@@ -166,7 +187,7 @@ export default defineComponent({
                   </div>
                 </div>
               )}
-              <div class={styles.tips}>{evaluatingData.resultData.clxtip}</div>
+              <div class={styles.tips}>{state.isPercussion ? evaluatingData.resultData.djytip : evaluatingData.resultData.clxtip}</div>
               <div class={styles.ctrls}>
                 <img src={zlycImg} class={[styles.ctrlsBtn, "evaluting-result-2"]} onClick={() => emit("close", "tryagain")} />
                 {evaluatingData.resultData.recordId ? (
@@ -190,7 +211,7 @@ export default defineComponent({
                     ) : null}
                   </div>
                 ) : null}
-                <img src={ckzpImg} class={[styles.ctrlsBtn, "evaluting-result-4", data.saveLoading ? styles.disablued : ""]} onClick={() => emit("close", "look")} />
+                <img src={ckzpImg} class={[styles.ctrlsBtn, "evaluting-result-4"]} onClick={() => emit("close", "look")} />
               </div>
             </div>
 

+ 47 - 14
src/page-instrument/evaluat-model/index.tsx

@@ -3,7 +3,7 @@ import { connectWebsocket, evaluatingData, handleEndBegin, handleStartBegin, han
 import Earphone from "./earphone";
 import styles from "./index.module.less";
 import SoundEffect from "./sound-effect";
-import state, { handleRessetState, resetPlaybackToStart, musicalInstrumentCodeInfo, clearSelection, initSetPlayRate, resetBaseRate } from "/src/state";
+import state, { handleRessetState, resetPlaybackToStart, clearSelection, initSetPlayRate, resetBaseRate } from "/src/state";
 import { storeData } from "/src/store";
 import { browser } from "/src/utils";
 import { getNoteByMeasuresSlursStart } from "/src/helpers/formateMusic";
@@ -178,6 +178,8 @@ export default defineComponent({
       let skip = false;
       const datas = [];
       let selectTimes = state.times;
+      // 选段评测前面小节的listen、play标识
+      let preLyricsContent = ''
       let unitTestIdx = 0;
       let preTime = 0;
       let preTimes = [];
@@ -207,16 +209,38 @@ export default defineComponent({
       actualBeatLength = preTimes.length ? actualBeatLength + preTimes[preTimes.length - 1].relaMeasureLength * 1000 : actualBeatLength;
       // 如果是弱起,并且预备小节是第一节
       if (state.section.length && state.sectionFirst && state.sectionFirst.measureListIndex == 0) {
-        actualBeatLength = actualBeatLength < Math.round((state.times[0].fixtime * 1000) / 1) ? Math.round((state.times[0].fixtime * 1000) / 1) : actualBeatLength;
+        // actualBeatLength = actualBeatLength < Math.round((state.times[0].fixtime * 1000) / 1) ? Math.round((state.times[0].fixtime * 1000) / 1) : actualBeatLength;
       }
       
       let firstNoteTime = unitTestIdx > 1 ? preTime : 0;
       let measureIndex = -1;
       let recordMeasure = -1;
-
+      
+      // 如果有mp3节拍器,并且预备小节是第一节,并且从0开始播放,actualBeatLength需要加上mp3节拍器时间
+      if (state.section.length === 2 && firstNoteTime === 0 && state.section[0]?.MeasureNumberXML === state.firstMeasureNumber + 1 && state.times[0].fixtime) {
+        actualBeatLength  = actualBeatLength + Math.round((state.times[0].fixtime * 1000) / 1)
+      }
+      // 找到选段评测,开始小节前面最近的是play或者listen的小节
+      if (preTimes.length) {
+        for (let index = preTimes.length-1; index >= 0; index--) {
+          const item = preTimes[index]
+          // const note = getNoteByMeasuresSlursStart(item)
+          const note = item
+          if (note.formatLyricsEntries.contains('Play') || note.formatLyricsEntries.contains('Play...')) {
+            preLyricsContent = 'Play'
+            break
+          }
+          if (note.formatLyricsEntries.contains('Listen')) {
+            preLyricsContent = 'Listen'
+            break
+          }
+        }
+        preLyricsContent = preLyricsContent ? preLyricsContent : 'Play'
+      } 
       for (let index = 0; index < selectTimes.length; index++) {
         const item = selectTimes[index];
-        const note = getNoteByMeasuresSlursStart(item);
+        // const note = getNoteByMeasuresSlursStart(item);
+        const note = item;
         // #8701 bug: 评测模式,是以曲谱本身的速度进行评测,所以rate取1,不需要转换
         // const rate = state.speed / state.originSpeed;
         const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
@@ -226,6 +250,10 @@ export default defineComponent({
         const end = difftime + (item.sourceRelaEndtime || item.relaEndtime) - starTime;
         const isStaccato = note.noteElement.voiceEntry.isStaccato();
         const noteRate = isStaccato ? 0.5 : 1;
+        // 如果选段评测,开始小节没有注脚,则取前面最近的小节的注脚
+        if (index == 0 && !note.formatLyricsEntries.length) {
+          ListenMode = preLyricsContent === 'Play' ? false : preLyricsContent === 'Listen' ? true : false
+        }        
         if (note.formatLyricsEntries.contains("Play") || note.formatLyricsEntries.contains("Play...")) {
           ListenMode = false;
         }
@@ -446,7 +474,7 @@ export default defineComponent({
       // 非选段状态,从头开始评测,重置速度
       if (!state.sectionStatus && state.section.length === 0) {
         state.activeNoteIndex = 0;
-        state.activeMeasureIndex = 1;
+        state.activeMeasureIndex = state.firstMeasureNumber === 0 ? 0 : 1;
         state.speed = state.times[0].measureSpeed * state.basePlayRate
       }
       initSetPlayRate();
@@ -523,7 +551,8 @@ export default defineComponent({
       // 如果打开了延迟检测开关,需要先发送开始检测的消息
       const delayData = await api_getDeviceDelay();
       console.log('设备的延迟值',delayData.content?.value)
-      if (delayData && delayData.content?.value <= 0 && state.paymentType !== "LOCK") {
+      // 管理端链接上有systemType=web的参数,管理端人员的paymentType是lock,管理端人员需要进行延迟检测,非管理端非会员不进行延迟检测
+      if (delayData && delayData.content?.value <= 0 && (state.paymentType !== "LOCK" || query.systemType === 'web') ) {
         await api_startDelayCheck({});
       } else {
         evaluatingData.checkEnd = true;
@@ -626,14 +655,18 @@ export default defineComponent({
         </Popup>
 
         {/* 评测作业,非完整评测不显示评测结果弹窗 */}
-        {evaluatingData.hideResultModal ? (
-          <EvaluatResult onClose={handleEvaluatResult} />
-        ) : (
-          <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={evaluatingData.resulstMode}>
-            <EvaluatResult onClose={handleEvaluatResult} />
-          </Popup>
-        )}
-
+        {
+          evaluatingData.resulstMode && 
+          <>
+            {evaluatingData.hideResultModal ? (
+              <EvaluatResult onClose={handleEvaluatResult} />
+            ) : (
+              <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={evaluatingData.resulstMode}>
+                <EvaluatResult onClose={handleEvaluatResult} />
+              </Popup>
+            )}   
+          </>       
+        }
         <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={evaluatModel.evaluatUpdateAudio}>
           <EvaluatAudio onClose={hanldeUpdateVideoAndAudio} />
         </Popup>

+ 10 - 7
src/page-instrument/header-top/index.tsx

@@ -32,6 +32,7 @@ import { smoothAnimationState } from "../view-detail/smoothAnimation";
 import { isMusicList, musicListShow } from "../component/the-music-list";
 import { EvaluatingDriver, FollowDriver, PractiseDriver } from "../custom-plugins/guide-driver";
 import { fingerRef } from "/src/page-instrument/view-detail/index"
+import { handleLoadBeatMusic } from "/src/view/audio-list"
 
 const ModeView = defineAsyncComponent(() =>
   import('./modeView')
@@ -122,7 +123,7 @@ let isClickMode = false;
  * @param oldPlaySource  没改变之前的播放类型
  * @param isforceReset   是否强制刷新播放状态 模式times时值改变时候也刷新
  */
-export function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: IPlayState, isforceReset?: boolean) {
+export async function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: IPlayState, isforceReset?: boolean) {
   const isModeChange = modeChangeHandleTimes(oldPlayType, oldPlaySource);
   // 没有切换的时候 不处理下面的
   if (isModeChange) {
@@ -137,6 +138,8 @@ export function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: I
     // 隐藏重播按钮
     resetBtn && (resetBtn.value.display = false);
   }
+  // 节拍器音频加载
+  await handleLoadBeatMusic()
   // 当模式改变的时候  放在这里是因为需要等谱面加载完成之后再提示(点击按钮模式切换才提示)
   if (isClickMode) {
     showToast({
@@ -376,14 +379,14 @@ export default defineComponent({
       // 总谱渲染在播放过程中 不能切换 
       if(state.isCombineRender && state.playState === "play") return { display: true, disabled: true } 
       if (!state.isAppPlay) {
+        // 播放过程中不能切换
+        if (state.playState === "play") {
+          return { display: true, disabled: true };
+        }
         if (state.playType === "play") {
           // 原声, 伴奏 少一个,就不能切换
           if (state.music && state.accompany) return { display: true, disabled: false };
         } else {
-          // 播放过程中不能切换
-          if (state.playState === "play") {
-            return { display: true, disabled: true };
-          }
           // 范唱
           let index = 0;
           state.fanSong && index++;
@@ -808,7 +811,7 @@ export default defineComponent({
               id={state.platform === IPlatform.PC ? "teacherTop-1" : "studnetT-1"}
               style={{ display: originBtn.value.display ? "" : "none" }}
               class={["driver-3", styles.btn, originBtn.value.disabled && styles.disabled, state.playType === "play" ? styles.playSource : styles.songSource]}
-              onClick={() => {
+              onClick={async () => {
                 const oldPlayType = state.playType;
                 const oldPlaySource = state.playSource;
                 if (state.playType === "play") {
@@ -822,7 +825,7 @@ export default defineComponent({
                     state.playSource = state.fanSong ? "music" : "background";
                   }
                 }
-                handlerModeChange(oldPlayType, oldPlaySource);
+                await handlerModeChange(oldPlayType, oldPlaySource);
                 // 总谱 并且开启了单个声轨音频时候
                 if(state.isCombineRender && state.playSource === "background") {
                   audioData.combineIndex = -1

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

@@ -17,6 +17,7 @@ import Dragbom from "/src/view/plugins/useDrag/dragbom";
 import { storeData } from "/src/store";
 import { getGuidance, setGuidance } from "../../custom-plugins/guide-page/api";
 import { metronomeData } from "/src/helpers/metronome";
+import { handleLoadBeatMusic } from "/src/view/audio-list"
 
 export default defineComponent({
 	name: "settting",
@@ -140,7 +141,7 @@ export default defineComponent({
                             </div>
                         </div>
                         {
-                            state.modeType === 'practise' && state.playType === "sing" && state.mingSong && state.mingSongGirl &&
+                            state.modeType === 'practise' && state.playSource === "mingSong" && state.mingSong && state.mingSongGirl &&
                             <div class={styles.cellBox}>
                                 <div class={styles.tit}>唱名类型</div>
                                 <div class={styles.radioBox}>
@@ -151,6 +152,8 @@ export default defineComponent({
                                                     return
                                                 }
                                                 audioData.mingSongType = item.value as 0|1
+                                                // 加载节拍器音频
+                                                handleLoadBeatMusic()
                                                 changeMingSongType()
                                             } }>{item.name}</div>
                                         })

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

@@ -9,11 +9,12 @@ import { getQuery } from "/src/utils/queryString";
 import { api_createMusicPlayer, api_updateMusicPlayer, api_checkSocketStatus } from "/src/helpers/communication";
 import { storeData } from "/src/store";
 import { data as workData } from "/src/page-instrument/custom-plugins/work-index";
+import { handleLoadBeatMusic } from "/src/view/audio-list"
 
 export default defineComponent({
 	name: "speed",
 	setup() {
-		const speed = ref(state.speed);
+		const speed = ref(Math.floor(state.speed));
 		const switchLoading = ref(false);
 		const query: any = getQuery();
 		const minusSpeed = () => {
@@ -63,7 +64,10 @@ export default defineComponent({
 		})
 		// 切换节拍器
 		const toggleSwitch = async (res: any) => {
+			switchLoading.value = true;
 			metronomeDisable.value = res;
+			await handleLoadBeatMusic()
+			switchLoading.value = false;
 			return
 			switchLoading.value = true;
 			try {

+ 19 - 1
src/page-instrument/main.ts

@@ -13,6 +13,7 @@ import "./theme.css";
 import "./custom-plugins/guide-driver/index.less"
 import { getQuery } from "/src/utils/queryString";
 import { getRequestHostname } from "/src/utils"
+import useErrorLog from "/src/hooks/errorLog"
 
 (function () {
 	const query = getQuery();
@@ -34,4 +35,21 @@ import { getRequestHostname } from "/src/utils"
 	});
 })();
 
-createApp(App).use(router).mount("#app");
+
+
+const app = createApp(App)
+
+app.use(router)
+
+//createApp(App).use(router).mount("#app");
+
+// app.config.errorHandler = (err, instance, info) => {
+// 	console.error('Vue全局错误:', err, info);
+// };
+  
+
+// 监听错误信息
+const errorLog = useErrorLog();
+errorLog.startListenErrorLog();
+
+app.mount("#app")  

+ 2 - 3
src/page-instrument/simple-detail/index.tsx

@@ -6,7 +6,7 @@ import { getQuery } from "/src/utils/queryString";
 import { closeToast, showLoadingToast } from "vant";
 import store from "store";
 import { formateTimes } from "../../helpers/formateMusic";
-import { setCustomGradual, setCustomNoteRealValue } from "/src/helpers/customMusicScore"
+import { setCustomNoteRealValue } from "/src/helpers/customMusicScore"
 import { initSmoothAnimation, smoothAnimationState, destroySmoothAnimation, moveSmoothAnimationByPlayTime } from "../view-detail/smoothAnimation";
 import { api_cloudLoading, simple_musicPage } from "/src/helpers/communication";
 
@@ -125,8 +125,7 @@ export default defineComponent({
 			if (saveSpeed) {
 				handleSetSpeed(saveSpeed);
 			}
-			setCustomGradual();
-			setCustomNoteRealValue();
+			// setCustomNoteRealValue();
 			state.times = formateTimes(osmd);
 			console.log("🚀 ~ state.times:", state.times, state);
 			nextTick(() => {

+ 43 - 14
src/page-instrument/view-detail/index.tsx

@@ -27,7 +27,7 @@ import TheMusicList, { isMusicList } from "../component/the-music-list";
 import { storeData } from "/src/store";
 import ViewFigner from "../view-figner";
 import ToggleMusicSheet from "/src/view/plugins/toggleMusicSheet";
-import { setCustomGradual, setCustomNoteRealValue } from "/src/helpers/customMusicScore";
+import { setCustomNoteRealValue } from "/src/helpers/customMusicScore";
 import { usePageVisibility } from "@vant/use";
 import { initMidi } from "/src/helpers/midiPlay";
 import TheAudio from "/src/components/the-audio";
@@ -41,6 +41,7 @@ import ExerciseStatistics from "../custom-plugins/ExerciseStatistics"
 import { musicData } from "/src/view/music-score"
 import Vip from "/src/page-instrument/component/vip"
 import { getSvgPngToSize } from "/src/helpers/svgToPng"
+import { uploadErrorLog } from '/src/hooks/errorLog/uploadLog'
 // import bgJson from "./images/index.json";
 
 // const DelayCheck = defineAsyncComponent(() =>
@@ -117,6 +118,7 @@ export default defineComponent({
         }
       }
     };
+    console.time('加载过程')
     onBeforeMount(async () => {
       // console.time("渲染加载耗时");
       api_keepScreenLongLight();
@@ -128,14 +130,15 @@ export default defineComponent({
         Object.assign(state.setting, settting);
         //state.setting.beatVolume = state.setting.beatVolume || 50
         state.setting.beatVolume = 50;
-        if (state.setting.camera) {
-          const res = await api_openCamera();
-          // 没有授权
-          if (res?.content?.reson) {
-            state.setting.camera = false;
-            store.set("musicscoresetting", state.setting);
-          }
-        }
+        // 默认进来不需要开启摄像头,进入评测页面才需要判断是否开启摄像头
+        // if (state.setting.camera) {
+        //   const res = await api_openCamera();
+        //   // 没有授权
+        //   if (res?.content?.reson) {
+        //     state.setting.camera = false;
+        //     store.set("musicscoresetting", state.setting);
+        //   }
+        // }
       }
     });
 
@@ -176,8 +179,10 @@ export default defineComponent({
       state.guideInfo = guideInfoStore
       try { 
         await getMusicDetail(id);
-      } catch (err) {
+      } catch (err: any) {
         console.error(err);
+        const contentError = `reason: ${err?.message || ''};stack: ${err?.stack || ''};bizId: ${state.examSongId || query.id || ''};partIndex: ${query["part-index"] || state.partIndex || 0};partName: ${decodeURIComponent(query["part-name"] || '') || ''};`;
+        uploadErrorLog(contentError)
         state.isLoading = false;
         isEmptyMusicShow.value = true
         // 需要向外面(iframe)派发计时器数据的时候触发
@@ -207,10 +212,32 @@ export default defineComponent({
 
     /** 渲染完成 */
     const handleRendered = (osmd: any) => {
+      state.isLoading = false
       api_cloudLoading();
       console.timeEnd("渲染加载耗时");
+      if (typeof console.timeLog === 'function') {
+        console.timeLog('加载过程','谱面渲染完成')
+      } else {
+        console.log('加载过程','谱面渲染完成')
+      }
       detailData.skeletonLoading = false;
       state.osmd = osmd;
+      // 预览模式不需要往下执行
+      if (state.isPreView) {
+          // 管乐迷曲谱详情页,需要下载A4尺寸的图片
+          setTimeout(() => {
+            if (query.downPng === 'A4' && state.partIndex != 999) {
+              const imgList = getSvgPngToSize(state.osmd)
+              console.log('A4', imgList)
+              window.parent.postMessage({
+                api: 'musicStaffRender',
+                loading: false,
+                osmdImg: imgList
+              }, '*');
+            }
+          }, 100);
+        return;
+      }
       // 没有设置速度使用读取的速度
       if (state.originSpeed === 0) {
         state.originSpeed = state.speed = (osmd as any).bpm || osmd.Sheet.userStartTempoInBPM || 100;
@@ -220,8 +247,7 @@ export default defineComponent({
       // if (saveSpeed) {
       //   handleSetSpeed(saveSpeed);
       // }
-      setCustomGradual();
-      setCustomNoteRealValue();
+      // setCustomNoteRealValue();
       state.times = formateTimes(osmd);
       // state.times = resetFrequency(state.times);
       state.times = setNoteHalfTone(state.times);
@@ -344,6 +370,8 @@ export default defineComponent({
         handleRendered(osmd)
       }catch(err:any){
         console.log(err, "err")
+        const contentError = `reason: ${err?.message || ''};stack: ${err?.stack || ''};bizId: ${state.examSongId || query.id || ''};partIndex: ${query["part-index"] || state.partIndex || 0};partName: ${decodeURIComponent(query["part-name"] || '') || ''};`;
+        uploadErrorLog(contentError)
         // 需要向外面(iframe)派发计时器数据的时候触发
         if(query.isbeatTimes){
           console.log("webApi_beatTimes",err)
@@ -356,6 +384,7 @@ export default defineComponent({
           );
         }
       }
+      // handleRendered(osmd)
     }
     /** 指法配置 */
     const fingerConfig = computed<any>(() => {
@@ -612,8 +641,8 @@ export default defineComponent({
           )}
         </div>
         {/* 曲目渲染完成,再去下载mp3资源 */}
-        {!detailData.isLoading && !detailData.skeletonLoading && <AudioList />}
-
+        {!detailData.isLoading && <AudioList />}
+        {/* {!detailData.isLoading && !detailData.skeletonLoading && <AudioList />} */}
         {/* {!detailData.isLoading && <TheAudio src={tickWav} />} */}
 
         {/* 预加载延迟检测组建 */}

+ 14 - 64
src/page-instrument/view-evaluat-report/index.tsx

@@ -84,6 +84,7 @@ export default defineComponent({
       musicalNotesPlayStats: [] as any[],
       userMeasureScore: {} as any,
       isNewReport: true,
+      isSpecialReport: false, // 是否是特殊的评测记录(含有listent、play等曲子的评测记录,非选段状态下可能返回的评测数据不是从第1小节开始的,这种情况需要兼容处理)
     });
     const getAPPData = async () => {
       const screenData = await isSpecialShapedScreen();
@@ -101,68 +102,6 @@ export default defineComponent({
       api_setStatusBarVisibility();
     });
     // console.log(route.params, query)
-    /** 获取曲谱数据 */
-    const getMusicInfo = (res: any) => {
-      const index = state.partIndex;
-      const musicInfo = {
-        ...res.data,
-        ...res.data.background[index],
-      };
-      // console.log("🚀 ~ musicInfo:", musicInfo);
-      setState(musicInfo, index);
-      setCustom();
-      detailData.isLoading = false;
-    };
-
-    const setState = (data: any, index: number) => {
-      // console.log("🚀 ~ data:", data)
-      state.scrollContainer = "scrollContainer";
-      state.detailId = data.id;
-      state.xmlUrl = data.xmlFileUrl;
-      state.partIndex = index;
-      state.subjectId = data.musicSubject;
-      state.categoriesId = data.categoriesId;
-      state.categoriesName = data.musicTagNames;
-      state.enableEvaluation = data.canEvaluate ? true : false;
-      state.examSongId = data.id + "";
-      state.examSongName = data.musicSheetName;
-      // 解析扩展字段
-      if (data.extConfigJson) {
-        try {
-          state.extConfigJson = JSON.parse(data.extConfigJson as string);
-        } catch (error) {
-          console.error("解析扩展字段错误:", error);
-        }
-      }
-      state.isOpenMetronome = data.mp3Type === "MP3_METRONOME" ? true : false;
-      state.needTick = data.isOpenMetronome;
-      state.isShowFingering = data.showFingering ? true : false;
-      state.music = data.audioFileUrl;
-      state.accompany = data.metronomeUrl || data.metronomeUrl;
-      state.midiUrl = data.midiUrl;
-      state.parentCategoriesId = data.musicTag;
-      state.playMode = data.audioType === "MP3" ? "MP3" : "MIDI";
-      state.originSpeed = state.speed = data.speed;
-      state.track = data.track;
-      state.enableNotation = data.notation ? true : false;
-
-      // 映射声部ID
-      state.subjectId = mappingVoicePart(state.subjectId as any, "ORCHESTRA");
-      // console.log("🚀 ~ state.subjectId:", state.subjectId);
-      // 是否打击乐
-      state.isPercussion = state.subjectId == 23 || state.subjectId == 113 || state.subjectId == 121 || isRhythmicExercises();
-
-      // 设置指法
-      state.fingeringInfo = subjectFingering(state.subjectId);
-      // console.log("🚀 ~ state.fingeringInfo:", state.fingeringInfo, state.subjectId, state.track)
-      // state.isOpenPrepare = true
-    };
-
-    const setCustom = () => {
-      if (state.extConfigJson.multitrack) {
-        setGlobalData("multitrack", state.extConfigJson.multitrack);
-      }
-    };
 
     onMounted(async () => {
       state.isEvaluatReport = true;
@@ -196,6 +135,8 @@ export default defineComponent({
       // 评测报告展示什么类型的谱面
       state.isSingleLine = false;
       scoreData.musicType = query.musicRenderType ? query.musicRenderType : resultData.musicType ? resultData.musicType : state.musicRenderType;
+      // 如果是打击乐,只显示节奏一栏,itemType需要修改为'cadence'
+      scoreData.itemType = state.isPercussion ? 'cadence' : 'intonation';
       // @ts-ignore
       state.musicRenderType = scoreData.musicType;
       detailData.isLoading = false;
@@ -271,7 +212,10 @@ export default defineComponent({
       // console.log(1111,notes)
       for (const note of notes) {
         const idx = note.musicalNotesIndex !== undefined ? note.musicalNotesIndex : note.index;
-        const active = detailData.isNewReport ? notes[0]?.measureRenderIndex != 0 ? allNote.value[idx] : allNote.value.find((item: any) => item.i === idx) : allNote.value[idx];
+        let active = detailData.isNewReport ? notes[0]?.measureRenderIndex != 0 ? allNote.value[idx] : allNote.value.find((item: any) => item.i === idx) : allNote.value[idx];
+        if (detailData.isSpecialReport) {
+          active = allNote.value.find((item: any) => item.i === idx)
+        }    
         setTimeout(() => {
           if (!active?.id) return;
           if (active?.id && useedid.value.includes(active?.id)) {
@@ -421,12 +365,18 @@ export default defineComponent({
       console.log("🚀 ~ state.times:", allNote.value);
       // @ts-ignore
       const startMeasureNum = detailData.musicalNotesPlayStats?.[0]?.measureRenderIndex, endMeasureNum = detailData.musicalNotesPlayStats?.last()?.measureRenderIndex;
-      allNote.value = allNote.value.filter((item: any) => (item.MeasureNumberXML >= startMeasureNum+1 && item.MeasureNumberXML <= endMeasureNum+1))
+      // 从0开始的曲子,MeasureNumberXML也是从0开始,需要兼容处理
+      if (state.firstMeasureNumber === 0) {
+        allNote.value = allNote.value.filter((item: any) => (item.MeasureNumberXML >= startMeasureNum && item.MeasureNumberXML <= endMeasureNum))
+      } else {
+        allNote.value = allNote.value.filter((item: any) => (item.MeasureNumberXML >= startMeasureNum+1 && item.MeasureNumberXML <= endMeasureNum+1))
+      }
       // @ts-ignore
       const beams = Array.from(new Set(document.getElementsByClassName("vf-beam")));
       beams.forEach((item: any) => {
         item.classList.add(styles.beam);
       });
+      detailData.isSpecialReport = startMeasureNum > 0 && detailData.musicalNotesPlayStats?.[0]?.musicalNotesIndex != 0;
       //setPathColor();
       setViewColor();
       // setMearureColor();

+ 145 - 251
src/state.ts

@@ -8,22 +8,25 @@ import { IFingering, mappingVoicePart, subjectFingering, matchVoicePart } from "
 import { handleStartTick, closeTick } from "./view/tick";
 import { audioListStart, getAudioCurrentTime, getAudioDuration, setAudioCurrentTime, setAudioPlaybackRate, audioData } from "./view/audio-list";
 import { toggleFollow } from "./view/follow-practice";
-import { browser, setStorageSpeed, setGlobalData } from "./utils";
+import { browser, setStorageSpeed, setGlobalData, checkDecimal } from "./utils";
 import { api_cloudGetMediaStatus, api_createMusicPlayer, api_cloudChangeSpeed, api_cloudSuspend, api_cloudSetCurrentTime, api_cloudDestroy } from "./helpers/communication";
 import { verifyCanRepeat, getDuration, xmlAddPartName } from "./helpers/formateMusic";
-import { getMusicSheetDetail } from "./utils/baseApi"
+import { getMusicSheetDetail, getInstrumentCode } from "./utils/baseApi"
 import { getQuery } from "/src/utils/queryString";
 import { followData, skipNotePractice } from "/src/view/follow-practice/index"
 import { changeSongSourceByBeat } from "/src/view/audio-list"
 import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTime, moveTranslateXNum, destroySmoothAnimation, calcClientWidth } from "/src/page-instrument/view-detail/smoothAnimation"
 import { storeData } from "/src/store";
-import { downloadXmlStr } from "./view/music-score"
+import { downloadXmlStr, xmlDocRef } from "./view/music-score"
 import { musicScoreRef, headerColumnHide } from "/src/page-instrument/view-detail/index"
 import { headTopData } from "/src/page-instrument/header-top/index";
 import { api_lessonTrainingTrainingStudentDetail } from "/src/page-instrument/api"
 import { undoData, moveData } from "/src/view/plugins/move-music-score"
 import { HANDLE_WORK_ADD } from "/src/page-instrument/custom-plugins/work-index";
 import { speedBeatTo, unitImgs } from "/src/helpers/beatConfig"
+import IndexedDBService from "/src/utils/indexedDB";
+import { musicalInstrumentCodeInfo, instruments, fixInstrumentNameCode } from "/src/constant/instruments";
+import evaluatModel from "./page-instrument/evaluat-model";
 
 const query: any = getQuery();
 
@@ -60,229 +63,6 @@ export type ISonges = {
  */
 const classids = [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 30, 31, 35, 36, 38, 108, 150, 151, 152, 153, 154, 155, 156, 157, 158, 178, 179, 180, 181, 182]; // 大雅金唐, 竖笛教程, 声部训练展开的分类ID
 
-// 乐器code码
-export const musicalInstrumentCodeInfo = [
-  {
-    name: '长笛',
-    code: 'Flute',
-    id: 1
-  },
-  {
-    name: '短笛',
-    code: 'Piccolo',
-    id: 2
-  },
-  {
-    name: '单簧管',
-    code: 'Clarinet',
-    id: 3
-  },
-  {
-    name: '低音单簧管',
-    code: 'Bass Clarinet',
-    id: 4
-  },
-  {
-    name: '中音萨克斯',
-    code: 'Alto Saxophone',
-    id: 5
-  },
-  {
-    name: '次中音萨克斯',
-    code: 'Tenor Saxophone',
-    id: 6
-  },
-  {
-    name: '高音萨克斯',
-    code: 'Soprano Saxophone',
-    id: 7
-  },
-  {
-    name: '上低音萨克斯',
-    code: 'Baritone Saxophone',
-    id: 8
-  },
-  {
-    name: '双簧管',
-    code: 'Oboe',
-    id: 9
-  },
-  {
-    name: '大管',
-    code: 'Bassoon',
-    id: 10
-  },
-  {
-    name: '小号',
-    code: 'Trumpet',
-    id: 11
-  },
-  {
-    name: '圆号',
-    code: 'Horn',
-    id: 12
-  },
-  {
-    name: '长号',
-    code: 'Trombone',
-    id: 13
-  },
-  {
-    name: '上低音号',
-    code: 'Baritone',
-    id: 14
-  },
-  {
-    name: '次中音号',
-    code: 'Euphonium',
-    id: 15
-  },
-  {
-    name: '大号',
-    code: 'Tuba',
-    id: 16
-  },
-  {
-    name: '钢琴',
-    code: 'Piano',
-    id: 17
-  },
-  {
-    name: '电钢琴',
-    code: 'Electronical Piano',
-    id: 18
-  },
-  {
-    name: '钢片琴',
-    code: 'Glockenspiel',
-    id: 19
-  },
-  {
-    name: '小提琴',
-    code: 'Violin',
-    id: 20
-  },
-  {
-    name: '中提琴',
-    code: 'Viola',
-    id: 21
-  },
-  {
-    name: '大提琴',
-    code: 'Violoncello',
-    id: 22
-  },
-  {
-    name: '低音提琴',
-    code: 'Contrabass',
-    id: 23
-  },
-  {
-    name: '架子鼓',
-    code: 'Drum Set',
-    id: 24
-  },
-  {
-    name: '小鼓',
-    code: 'Snare Drum',
-    id: 25
-  },
-  {
-    name: '马林巴',
-    code: 'Marimba',
-    id: 26
-  },
-  {
-    name: '颤音琴',
-    code: 'Vibraphone',
-    id: 27
-  },
-  {
-    name: '钟琴',
-    code: 'Chimes',
-    id: 28
-  },
-  {
-    name: '木琴',
-    code: 'Xylophone',
-    id: 29
-  },
-  {
-    name: '管钟',
-    code: 'Tubular Bells',
-    id: 30
-  },
-  {
-    name: '定音鼓',
-    code: 'Timpani',
-    id: 31
-  },
-  {
-    name: '键盘',
-    code: 'Mallets',
-    id: 32
-  },
-  {
-    name: '排箫',
-    code: 'Panpipes',
-    id: 33
-  },
-  {
-    name: '陶笛',
-    code: 'Ocarina',
-    id: 34
-  },
-  {
-    name: '陶笛',
-    code: 'Alto Ocarina',
-    id: 34
-  },
-  {
-    name: '葫芦丝',
-    code: 'Woodwind',
-    id: 35
-  },
-  {
-    name: '葫芦丝',
-    code: 'Hulusi',
-    id: 35
-  },
-  {
-    name: '口风琴',
-    code: 'Nai',
-    id: 36
-  },
-  {
-    name: '口风琴',
-    code: 'Melodica',
-    id: 36
-  },
-  {
-    name: '德式竖笛',
-    code: 'Tenor Recorder',
-    id: 37
-  },
-  {
-    name: '德式竖笛',
-    code: 'German Recorder',
-    id: 37
-  },
-  {
-    name: '英式竖笛',
-    code: 'Baroque Recorder',
-    id: 38
-  },
-  {
-    name: '高音陶笛',
-    code: 'Whistling',
-    id: 39
-  },
-  {
-    name: '高音陶笛',
-    code: 'Soprano Ocarina',
-    id: 39
-  },
-]
 
 const state = reactive({
   systemType: "" as "teacher" | "web" | "student",
@@ -570,7 +350,7 @@ const state = reactive({
   // 加载条
   isLoading: true,
   /** 加载中的文案 */
-  loadingText: '音频资源加载中,请稍后…',
+  loadingText: '资源加载中,请稍后…',
   /** 是否是简单的单行谱模式页面 */
   isSimplePage: false, 
   /** xml的速度和后台设置的速度,计算出的基础音频播放倍率 */
@@ -608,6 +388,12 @@ const state = reactive({
   firstMeasureNumber: 1,
   /** 是否是单声轨多声部的声轨 */
   isSingleMutliTrack: false,
+  /** 是否是来源于缓存的xml */
+  xmlFromStore: false,
+  /** 是否已经初始化评测音频,只有切了声轨后,才需要重新传音频,普通的切谱面(五线谱、简谱;单行谱、多行谱等)不需要重复传 */
+  evaluatAudioInitDone: false,
+  /** 是否使用原生评测服务 */
+  useNativeEvaluation: false,
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -747,7 +533,14 @@ const handlePlaying = () => {
       // 如果开启了预备拍
       const selectStartItem = state.sectionFirst ? state.sectionFirst : state.section[0];
       const selectEndItem = state.section[1];
-      if (currentTime - selectEndItem.endtime >= 0) {
+      // 如果选段播放结束,或者音频播放结束(判断条件:currentTime >= duration)
+      // console.log('时间',currentTime,duration)
+      /**
+       * TODO:兼容音频时长比xml时值短的曲子
+       * isAudioShort:音频比选段的xml时值短,部分手机最后一帧返回的currentTime会比duration小,在这里加上一帧的时间(0.1666~0.2)
+       */
+      const isAudioShort = duration < selectEndItem.endtime
+      if ( (currentTime - selectEndItem.endtime >= 0) || (isAudioShort && (currentTime+0.02 >= duration)) ) {
         console.log("选段播放结束", state.setting.repeatAutoPlay);
         // 如果为选段评测模式
         if (state.modeType === "evaluating" && state.isSelectMeasureMode) {
@@ -835,10 +628,14 @@ export const skipNotePlay = async (itemIndex: number, isStart = false, handType?
     if (item.measureSpeed && state.section.length < 2) {
       // console.log('速度3')
       state.speed = state.basePlayRate * 10000 * item.measureSpeed / 10000
+      // 如果是接近整数的小数,则取整
+      if ( checkDecimal(state.speed) ) {
+        state.speed = Math.round(state.speed)
+      }
     }
     setAudioCurrentTime(itemTime, itemIndex);
     // 一行谱,点击音符,或者播放完成,需要跳转音符位置
-    gotoNext(item, true);
+    gotoNext(item, true, handType);
     // 不需要播放节拍器的声音,因为音频带有节拍器的声音
     // metronomeData.metro?.sound(itemTime);
     metronomeData?.metro?.findMetronomePosition(itemTime);
@@ -864,7 +661,7 @@ export const skipNotePlay = async (itemIndex: number, isStart = false, handType?
 export const togglePlay = async (playState: "play" | "paused", isForceCLoseToast?:boolean) => {
   // 如果mp3资源还在加载中,给出提示
   if (!state.isAppPlay && !state.audioDone) {
-    if (!isForceCLoseToast) showToast('音频资源加载中,请稍后')
+    if (!isForceCLoseToast) showToast('资源加载中,请稍后...')
     return
   }
   // 播放之前  当为评测模式和不为MIDI时候按  是否禁用节拍器  切换音源
@@ -1058,7 +855,7 @@ const setCursorPosition = (note: any, cursor: any, flag?: string) => {
  * 跳转到下一个音符
  * 一行谱,点击音符,或者播放完成,需要跳转音符位置,增加参数skipNote
  **/
-export const gotoNext = (note: any, skipNote?: boolean) => {
+export const gotoNext = (note: any, skipNote?: boolean, handType?: string) => {
   // console.log(33333333333,state.activeNoteIndex,note.i)
   const num = note.i;
 
@@ -1128,7 +925,11 @@ export const gotoNext = (note: any, skipNote?: boolean) => {
   if (state.isSingleLine && state.playState === "paused") {
     moveSvgDom(skipNote);
   }
-  scrollViewNote();
+  if (handType === 'manual') {
+    // 手动点击不执行滚动屏幕方法
+  } else {
+    scrollViewNote();
+  }
 };
 /** 获取指定音符 */
 export const getNote = (currentTime: number) => {
@@ -1405,17 +1206,18 @@ export const scrollViewNote = (resetTop?: boolean) => {
     if (offsetTop === cursorElement.offsetTop || Math.abs(offsetTop - cursorElement.offsetTop) < 30) return;
   }
   offsetTop = cursorElement.offsetTop;
+  const animateType = browser().android ? "instant" : "smooth"
   if (offsetTop > (state.headTopHeight + 30)) {
     musicScrollTop = (offsetTop - state.headTopHeight - 30) * state.musicZoom
     musicAndSelection.scrollTo({
-      top: (offsetTop - state.headTopHeight - 30) * state.musicZoom,
-      behavior: "auto",
+      top: (offsetTop - state.headTopHeight - 100) * state.musicZoom,
+      behavior: animateType,
     });
   } else {
     musicScrollTop = 0
     musicAndSelection.scrollTo({
       top: 0,
-      behavior: "auto",
+      behavior: animateType,
     });
   }
 };
@@ -1448,12 +1250,17 @@ export default state;
 
 /** 初始化评测音频 */
 export const evaluatCreateMusicPlayer = () => {
-  return api_createMusicPlayer({
+  if (state.evaluatAudioInitDone) {
+    return;
+  }
+  api_createMusicPlayer({
     musicSrc: state.accompany || state.music, // 曲谱音频url
     // tuneSrc: "https://oss.dayaedu.com/cloud-coach/1686725501654check_music1_(1).mp3", //效音音频url
     tuneSrc: "https://oss.dayaedu.com/MECMP/1722593665681.mp3", //效音音频url
     checkFrequence: 496,
+    useNativeEvaluation: state.useNativeEvaluation // 是否使用原生评测服务
   });
+  state.evaluatAudioInitDone = true
 };
 
 
@@ -1466,19 +1273,61 @@ export const getMusicDetail = async (id: string, type?: string) => {
   }
 };
 
+// 获取后台配置的声轨编码
+const initInstrumentCode = async () => {
+  const res = await getInstrumentCode();
+  if (res?.code === 200 && res.data?.length) {
+    for (let item of res.data) {
+      const codes = item.code.split(',') || [item.code]
+      codes.forEach((code: any) => {
+        instruments[code] = item.name
+      })
+    }
+  }
+  // console.log('声轨codes',instruments)
+}
+
+// 判断有没有xml缓存,有则直接使用
+const queryMusicXml = async (id: string, xmlUr: string) => {
+  let xmlString = ''
+  const dbService = new IndexedDBService("MyDatabase", "MyStore");
+  console.time('缓存获取xml')
+  const storeXmlData = await dbService.get(id).then((data) => data );
+  if (storeXmlData && storeXmlData.xmlString) {
+    xmlString = storeXmlData && storeXmlData.xmlString
+    state.xmlFromStore = true;
+    console.timeEnd('缓存获取xml')
+    // 使用完后删除数据
+    dbService.delete(id)
+  } else {
+    state.xmlFromStore = false;
+    xmlString = await fetch(xmlUr).then((response) => response.text());
+  }
+  return xmlString;
+}
 
 const getMusicInfo = async (res: any) => {
+  try {
+    // 单行谱页面不需要调用此接口
+    if (!state.isSimplePage) {
+      await initInstrumentCode()
+    }
+  } catch (error) {
+    // console.log(error)
+  }
   // 是否支持总谱
   state.isScoreRender = res.data?.isScoreRender
   // 是否默认显示总谱
   state.defaultScoreRender = res.data?.defaultScoreRender
-  /* 获取声轨列表 */
-  let xmlString = await fetch(res.data.xmlFileUrl).then((response) => response.text());
+  state.useNativeEvaluation = res.data?.useNativeEvaluation
+  // let xmlString = await fetch(res.data.xmlFileUrl).then((response) => response.text());
+  let xmlString: string = await queryMusicXml(res.data.bizId + "", res.data.xmlFileUrl);
   xmlString = xmlAddPartName(xmlString);
   downloadXmlStr.value = xmlString //给musice-score 赋值xmlString 以免加载2次
-  const tracks = xmlToTracks(xmlString) //获取声轨列表  
+  /* 获取声轨列表 */
+  const tracks = xmlToTracks(xmlString)
   // 是否显示节拍器  (管乐迷 默认显示节拍器)
-  //state.isMixBeat = res.data?.isMixBeat  
+  state.isMixBeat = res.data?.isMixBeat  
   /* 设置partIndex */
   let partIndexs = query["part-index"] ? query["part-index"].split(",") : ["-1"] // -1为partIndex没有值的时候
   // 如果传入的是part-name,需要将part-name转换成part-index
@@ -1512,21 +1361,29 @@ const getMusicInfo = async (res: any) => {
   } else {
     (window as any).DYFirstTrackName = '';
   }
-  // 如果是作业模式,需要默认渲染当前学生声部对应的声轨,并且默认不显示总谱
+  /**
+   * 如果是作业模式,需要默认渲染当前学生声部对应的声轨,并且默认不显示总谱
+   * 2025.02.28 补充逻辑,作业支持用户切换声轨,如果切换了声轨,则选中用户切换后的声轨,用户切换声轨后,url链接会带有part-index参数,
+   *  通过有没有part-index区分作业有没有切换声轨,如果没有切换声轨,则还是默认选中学生当前声部的声轨
+   */
   if (state.isHomeWork && storeData.user?.instrumentId) {
-    const currentTrack = res.data.musicSheetSoundList.find((item: any) => item.musicalInstrumentId === storeData.user?.instrumentId)?.track;
-    if (currentTrack) {
-      partIndex = tracks.findIndex(item => item === currentTrack) || partIndex
-      state.defaultScoreRender = false
+    if (!query["part-index"]) {
+      const currentTrack = res.data.musicSheetSoundList.find((item: any) => item.musicalInstrumentId === storeData.user?.instrumentId)?.track;
+      if (currentTrack) {
+        partIndex = tracks.findIndex(item => item === currentTrack) || partIndex
+      }
     }
+    state.defaultScoreRender = false
   }
   // 设置音源  track 为当前的声轨 index为当前的
   const { track, index, musicalInstrumentId } = state.isSimplePage ? { track:tracks[0], index: state.partIndex, musicalInstrumentId: '' } : initMusicSource(res.data, tracks, partIndex, workRecordInstrumentId)
   // 这里返回的track可能和实际的对不上,所以重新筛选一下
   const realTrack = musicalInstrumentId && res.data?.musicalInstruments?.length ? res.data?.musicalInstruments.find((item: any) => item?.id == musicalInstrumentId)?.code?.split(',')?.[0] : '';
+  const instrumentCodes = musicalInstrumentId && res.data?.musicalInstruments?.length ? res.data?.musicalInstruments.find((item: any) => item?.id == musicalInstrumentId)?.code : '';
   const musicInfo = {
     ...res.data,
-    track: res.data.musicSheetType === 'CONCERT' ? track : realTrack
+    track: res.data.musicSheetType === 'CONCERT' ? track : realTrack,
+    instrumentCodes,
   };
   console.log("🚀 ~ musicInfo:", musicInfo);
   setState(musicInfo, index);
@@ -1534,9 +1391,12 @@ const getMusicInfo = async (res: any) => {
 //获取xml中的音轨数据
 function xmlToTracks(xmlString: string) {
   //console.time('domparse')
-  const xmlParse = new DOMParser().parseFromString(xmlString, "text/xml");
+  // console.time('解析xml 耗时1')
+  // const xmlParse = new DOMParser().parseFromString(xmlString, "text/xml");
+  const xmlParse = xmlDocRef.value;
+  // console.timeEnd('解析xml 耗时1')
   //console.timeEnd('domparse')
-  const partNames = Array.from(xmlParse.getElementsByTagName('part-name'));
+  const partNames = xmlParse ? Array.from(xmlParse.getElementsByTagName('part-name')) : [];
   return partNames.reduce((arr: string[], item) => {
     const textContent = item?.textContent?.trim()
     if (textContent?.toLocaleLowerCase() === "common") {
@@ -1694,7 +1554,7 @@ function initMusicSource(data: any, tracks: string[], partIndex: number, workRec
   }
    /*  目前 管乐迷没有用到 后台生成的节拍器 */
   // 当使用节拍器的时候才加载节拍器音频
-  if(state.isMixBeat && false) {
+  if(state.isMixBeat) {
     Object.assign(state.beatSong, {
       music: musicObj?.audioBeatMixUrl,
       accompany: accompanyObj?.audioBeatMixUrl,
@@ -1804,7 +1664,10 @@ const setState = (data: any, index: number) => {
   if (state.isSimplePage) {
     state.isCombineRender = false;
   }
-  setCustom(state.isCombineRender ? data.musicSheetSoundList?.length : 0);
+  // 多分轨合并显示的曲子,有可能只有一个原音文件,minCombineNum的最小值至少为2
+  const minCombineNum = data.musicSheetSoundList?.length ? Math.max(data.musicSheetSoundList?.length, 2) : 2;  
+  setCustom(state.isCombineRender ? minCombineNum : 0);
+  // setCustom(state.isCombineRender ? data.musicSheetSoundList?.length : 0);
   // 解析扩展字段
   if (data.extConfigJson) {
     try {
@@ -1869,7 +1732,37 @@ const setState = (data: any, index: number) => {
    * 获取指法code
    */
   // const code = state.isConcert ? matchVoicePart(state.trackId, "CONCERT") : matchVoicePart(state.musicalCodeId, "SINGLE");
-  const code = matchVoicePart(state.trackId, "CONCERT")
+
+  // 如果是midi的曲子,midi的曲子没有musicSheetSoundList原音列表,指法需要通过musicalInstruments字段判断
+  if (data.musicSheetType === "SINGLE" && data.playMode === 'MIDI' && data.musicalInstruments?.length) {
+    const currentInstrumentId = query.instrumentId || storeData.user?.instrumentId;
+    let midiTrackId = null
+    if (currentInstrumentId) {
+      midiTrackId = data.musicalInstruments.find((item: any) => item.id == currentInstrumentId)?.code?.split(',')?.[0]
+    } else {
+      midiTrackId = data.musicalInstruments[0]?.code?.split(',')?.[0]
+    }
+    state.trackId = midiTrackId || state.trackId
+  }
+
+  let code = matchVoicePart(state.trackId, "CONCERT")
+  /**
+   * 曲子:中音萨克斯教程2-4,返回的乐器code是"Alto Sax,Alto Saxophone",使用第一个Alto Sax去找,找不到对应的指法,这种情况下需要使用多个code去匹配指法
+   * 如果当前的第一code找不到,用instrumentCodes去找,
+   * 
+   * */ 
+  if ( data.instrumentCodes && (code == 1 || !code) ) {
+    for (let name of data.instrumentCodes.split(',')) {
+      let matchCode = matchVoicePart(name, "CONCERT")
+      if (matchCode && matchCode !== 1) {
+        code = matchCode
+        break;
+      }
+    }
+  }
+  if (code == 1 || !code) {
+    code = fixInstrumentNameCode(state.trackId)
+  }
   state.fingeringInfo = subjectFingering(code);
   console.log("🚀 ~ state.fingeringInfo:", code, state.fingeringInfo, state.trackId, state.track);
   state.musicalCodeId = state.fingeringInfo?.id || 0
@@ -2349,6 +2242,7 @@ export const checkMoveNoSave = async () => {
 
 /** 刷新谱面 */
 export const refreshMusicSvg = () => {
+  (window as any).DYhideTrackTune = false;
   moveData.noteCoords = []
   moveData.modelList = []
   clearSelection();

+ 15 - 0
src/utils/baseApi.ts

@@ -19,4 +19,19 @@ export const creatMusicSheetImg = (data: any) => {
     isContentCenter: true, // 内容平台
     data
   });
+};
+
+/** 获取总控平台乐器编码 */
+export const getInstrumentCode = () => {
+  const url = `/musicSheet/instrumentCode`;
+  return request.get(url);
+};
+
+/** 上传错误信息 */
+export const sysExceptionLogSave = (data: any): Promise<any> => {
+  return request.post('/sysExceptionLog/save', {
+    requestType: 'json',
+    isExceptionLog: true, // js异常收集,需要使用api-auth
+    data
+  });
 };

+ 7 - 1
src/utils/index.ts

@@ -158,4 +158,10 @@ export const debounce = (fn: Function, ms = 0) => {
 	  // @ts-ignore
 	  timeoutId = setTimeout(() => fn.apply(this, args), ms);
 	}
-}
+}
+
+// 使用正则表达式匹配小数点后第一位数字是否是 0 或 9
+export const checkDecimal = (num: number | string) => {
+	return /^\d*\.(0|9)/.test(num.toString());
+}
+

+ 117 - 0
src/utils/indexedDB.ts

@@ -0,0 +1,117 @@
+interface DataInfo {
+    id: number | string; // 主键
+    xmlString: string;
+    xmlDoc: Document;
+}
+
+export default class IndexedDBService<T> {
+    private dbName: string;
+    private storeName: string;
+    private version: number;
+  
+    constructor(dbName: string, storeName: string, version: number = 1) {
+      this.dbName = dbName;
+      this.storeName = storeName;
+      this.version = version;
+    }
+  
+    // 初始化数据库
+    private async init(): Promise<IDBDatabase> {
+      return new Promise((resolve, reject) => {
+        const request = indexedDB.open(this.dbName, this.version);
+  
+        // 数据库第一次创建或版本号升级时触发
+        request.onupgradeneeded = () => {
+          const db = request.result;
+          // 创建对象存储(类似于表)
+          if (!db.objectStoreNames.contains(this.storeName)) {
+            db.createObjectStore(this.storeName, { keyPath: "id" });
+          }
+        };
+  
+        // 返回数据库实例
+        request.onsuccess = () => resolve(request.result);
+        // 捕获错误
+        request.onerror = () => reject(request.error);
+      });
+    }
+  
+    // 添加或者更新数据
+    async save(data: T): Promise<void> {
+      const db = await this.init();
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readwrite");
+        const store = transaction.objectStore(this.storeName);
+        const request = store.put(data);
+  
+        request.onsuccess = () => resolve();
+        request.onerror = () => reject(request.error);
+      });
+    }
+  
+    // 获取某条数据
+    async get(id: number | string): Promise<T | undefined> {
+      const db = await this.init();
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readonly");
+        const store = transaction.objectStore(this.storeName);
+        const request = store.get(id);
+  
+        request.onsuccess = () => resolve(request.result as T);
+        request.onerror = () => reject(request.error);
+      });
+    }
+  
+    // 删除数据
+    async delete(id: number | string): Promise<void> {
+      const db = await this.init();
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readwrite");
+        const store = transaction.objectStore(this.storeName);
+        const request = store.delete(id);
+  
+        request.onsuccess = () => resolve();
+        request.onerror = () => reject(request.error);
+      });
+    }
+  
+    // 查询所有数据
+    async getAll(): Promise<T[]> {
+      const db = await this.init();
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readonly");
+        const store = transaction.objectStore(this.storeName);
+        const request = store.getAll();
+  
+        request.onsuccess = () => resolve(request.result as T[]);
+        request.onerror = () => reject(request.error);
+      });
+    }
+
+    // 清空某个 Object Store 中的所有数据
+    async clearAllData(): Promise<void> {
+      const db = await this.init();
+    
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readwrite");
+        const store = transaction.objectStore(this.storeName);
+    
+        const request = store.clear(); // 清空数据
+    
+        request.onsuccess = () => resolve();
+        request.onerror = () => reject(request.error);
+      });
+    }
+
+    // 删除整个数据库
+    async deleteDatabase(): Promise<void> {
+      return new Promise((resolve, reject) => {
+        const request = indexedDB.deleteDatabase(this.dbName); // 删除整个数据库
+    
+        request.onsuccess = () => resolve();
+        request.onerror = () => reject(request.error);
+      });
+    }
+}
+  
+  

+ 1 - 1
src/utils/request.ts

@@ -19,7 +19,7 @@ request.interceptors.request.use(
 	(url, options) => {
 		// console.log(9999,storeData.proxy,storeData.platformApi,options)
 		// 内容平台的前缀为cbs-app
-		const platformApi = options.isContentCenter ? '/cbs-app' : storeData.platformApi
+		const platformApi = options.isContentCenter ? '/cbs-app' : options.isExceptionLog ? '/api-auth' : storeData.platformApi
 		const _prefix = storeData.proxy + platformApi;
 		/** 
 		 * 只有后台才去设置

+ 143 - 29
src/view/audio-list/index.tsx

@@ -214,7 +214,8 @@ const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
 		return Promise.resolve(null)
 	}
 	return new Promise((resolve) => {
-		const a = new Audio(src + '?v=' + Date.now());
+		// const a = new Audio(src + '?v=' + Date.now());
+		const a = new Audio(src);
 		a.onloadedmetadata = () => {
 			resolve(a);
 		};
@@ -237,59 +238,154 @@ const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
 };
 
 // 合成节拍器资源
-const crunker = new Crunker()
-async function mergeBeatAudio(music?:string, accompany?:string){
-	let beatMusic, beatAccompany
+let CrunkerInstance: Crunker
+async function mergeBeatAudio(music?:string){
+	let beatMusic
 	if(!state.isMixBeat) {
-		return [beatMusic, beatAccompany]
+		return beatMusic
+	}
+	if(!music){
+		return beatMusic
 	}
 	console.time("音频合成时间")
 	try{
+		/* 音频合成 */
+		if(!CrunkerInstance){
+			CrunkerInstance = new Crunker()
+		}
 		console.time("音频加载时间")
-		const [musicBuff, accompanyBuff, tickBuff, tockBuff] = await crunker.fetchAudio(music?`${music}?v=${Date.now()}`:undefined, accompany?`${accompany}?v=${Date.now()}`:undefined, tickMp3, tockMp3)
+		const [musicBuff, tickBuff, tockBuff] = await CrunkerInstance.fetchAudio(music?`${music}`:undefined, tickMp3, tockMp3)
 		console.timeEnd("音频加载时间")
 		// 计算音频空白时间
-		const silenceDuration = musicBuff&&!state.isEvxml ? crunker.calculateSilenceDuration(musicBuff) : 0
-		const silenceBgDuration = accompanyBuff&&!state.isEvxml ? crunker.calculateSilenceDuration(accompanyBuff) : 0
-		console.log(`音频空白时间:${silenceDuration};${silenceBgDuration}`)
+		const silenceDuration = musicBuff&&!state.isEvxml ? CrunkerInstance.calculateSilenceDuration(musicBuff) : 0
+		console.log(`音频空白时间:${silenceDuration}`)
 		const beats:AudioBuffer[] = []
 		const beatsTime:number[] = []
-		const beatsBgTime:number[] = []
 		metronomeData.metroMeasure.map(measures=>{
 			measures.map((item:any)=>{
 				beats.push(item.isTick?tickBuff!:tockBuff!)
 				beatsTime.push(item.time + silenceDuration) // xml 计算的时候 加上空白的时间
-				beatsBgTime.push(item.time + silenceBgDuration) // xml 计算的时候 加上空白的时间 没有背景不赋值
 			})
 		})
 		console.time("音频合并时间")
-		const musicBuffMeg = musicBuff && crunker.mergeAudioBuffers([musicBuff,...beats],[0,...beatsTime])
-		const accompanyBuffMeg = accompanyBuff && crunker.mergeAudioBuffers([accompanyBuff,...beats],[0,...beatsBgTime])
+		const musicBuffMeg = musicBuff && CrunkerInstance.mergeAudioBuffers([musicBuff,...beats],[0,...beatsTime])
 		console.timeEnd("音频合并时间")
 		console.time("音频audioDom生成时间")
-		beatMusic = musicBuffMeg && crunker.exportAudioElement(musicBuffMeg)
-		beatAccompany = accompanyBuffMeg && crunker.exportAudioElement(accompanyBuffMeg)
+		beatMusic = musicBuffMeg && CrunkerInstance.exportAudioElement(musicBuffMeg)
 		console.timeEnd("音频audioDom生成时间")
 	}catch(err){
 		console.log(err)
 	}
 	console.timeEnd("音频合成时间")
-	return [beatMusic, beatAccompany]
+	return beatMusic
+}
+
+// 处理加载节拍器音频
+export const handleLoadBeatMusic = async () => {
+	if(metronomeData.disable) {
+		return
+	}
+	const playType = state.playType
+	const playSource = state.playSource
+	const mingSongType = audioData.mingSongType
+	// 当前模式下 如果已经有合成音频了,就不走合成逻辑了
+	let isBeatMusic = false
+	let currentMusic  //当前的音频
+	const beatPlayObj = {
+		"play_music":"beatSongEle",
+		"play_background":"beatBackgroundEle",
+		"sing_music":"beatFanSongEle",
+		"sing_background":"beatBanSongEle"
+	}
+	const playObj = {
+		"play_music":"music",
+		"play_background":"accompany",
+		"sing_music":"fanSong",
+		"sing_background":"banSong",
+	}
+	if(playSource === "mingSong") {
+		// 当男声女声都有的时候 才区分
+		if(state.mingSong && state.mingSongGirl){
+			isBeatMusic = mingSongType === 1 ? !!audioData.mingSongTypeCollection.beatMingSongEle : !!audioData.mingSongTypeCollection.beatMingSongGirlEle
+			currentMusic = mingSongType === 1 ? state.mingSong : state.mingSongGirl
+		}else{
+			isBeatMusic = !!audioData.mingSongTypeCollection.beatMingSongEle
+			currentMusic = state.mingSong
+		}
+	}else{
+		// @ts-ignore
+		isBeatMusic = !!audioData.songCollection[beatPlayObj[`${playType}_${playSource}`]]
+		// @ts-ignore
+		currentMusic = state[playObj[`${playType}_${playSource}`]]
+	}
+	if(isBeatMusic || !currentMusic){
+		return
+	}
+	state.loadingText = "资源加载中,请稍后…"
+	state.isLoading = true
+	const musicAudio = await mergeBeatAudio(currentMusic) as any
+	const playEleObj = {
+		"play_music":"beatSongEle",
+		"play_background":"beatBackgroundEle",
+		"sing_music":"beatFanSongEle",
+		"sing_background":"beatBanSongEle"
+	}
+	// 给音频赋值
+	if(playSource === "mingSong"){
+		// 当男声女声都有的时候 才区分
+		if(state.mingSong && state.mingSongGirl){
+			if(mingSongType === 1) {
+				audioData.mingSongTypeCollection.beatMingSongEle = musicAudio
+			}else {
+				audioData.mingSongTypeCollection.beatMingSongGirlEle = musicAudio
+			}
+		}else{
+			audioData.songCollection.beatMingSongEle = musicAudio
+			audioData.mingSongTypeCollection.beatMingSongEle = musicAudio
+		}
+		if(musicAudio){
+			musicAudio.addEventListener("play", onPlay);
+			musicAudio.addEventListener("ended", onEnded);
+		}
+		changeMingSongType()
+	}else{
+		if(playType === "play" && !audioData.songCollection.beatSongEle && !audioData.songCollection.beatBackgroundEle){
+			if(musicAudio){
+				musicAudio.addEventListener("play", onPlay);
+				musicAudio.addEventListener("ended", onEnded);
+			}
+		}		
+		if(playType === "sing" && !audioData.songCollection.beatFanSongEle && !audioData.songCollection.beatBanSongEle){
+			if(musicAudio){
+				musicAudio.addEventListener("play", onPlay);
+				musicAudio.addEventListener("ended", onEnded);
+			}
+		}
+		// @ts-ignore
+		audioData.songCollection[playEleObj[`${playType}_${playSource}`]] = musicAudio
+	}
+	state.isLoading = false
 }
+
 // 切换对应的声轨,并且配置当前的audio
 export async function changeCombineAudio (combineIndex: number){
+	const currentTime = getAudioCurrentTime()
 	// 重复点击的时候取消选中 原音
 	if(combineIndex === audioData.combineIndex){
 		audioData.combineIndex = -1
 		state.playSource = "background"
 		state.music = ""
+		// 当开启节拍器的时候,切为伴奏的时候合成节拍器1
+		await handleLoadBeatMusic()
 		// 当没有背景音文件的时候
 		if(!state.accompany) {
 			state.noMusicSource = true
 		}
+		//设置进度
+		setAudioCurrentTime(currentTime)
 		return
 	}
-	state.loadingText = "音频资源加载中,请稍后…";
+	state.loadingText = "资源加载中,请稍后…";
 	state.isLoading = true;
 	const musicUrl = audioData.combineMusics[combineIndex]
 	// 有就拿缓存,没有就加载
@@ -305,17 +401,20 @@ export async function changeCombineAudio (combineIndex: number){
 		audioData.combineMusicEles.push(...itemMusic)
 	}else{
 		const music = await createAudio(musicUrl)
-		const [beatMusic] = await mergeBeatAudio(musicUrl)
+		const beatMusic = await mergeBeatAudio(musicUrl)
 		// 当没有背景音的时候 需要绑定事件
-		if(!state.accompany){
+		if(!audioData.songCollection.backgroundEle){
 			if(music){
 				music.addEventListener("play", onPlay);
 				music.addEventListener("ended", onEnded);
 			}			
-			if(beatMusic){
-				beatMusic.addEventListener("play", onPlay);
-				beatMusic.addEventListener("ended", onEnded);
-			}
+		}
+		// 取消掉背景音绑定的时候,然后给当前原音节拍音频绑定事件,这样防止没有背景节拍的时候,能给
+		if(beatMusic){
+			audioData.songCollection.beatBackgroundEle?.removeEventListener("play", onPlay)
+			audioData.songCollection.beatBackgroundEle?.removeEventListener("ended", onEnded)
+			beatMusic.addEventListener("play", onPlay);
+			beatMusic.addEventListener("ended", onEnded);
 		}
 		audioData.combineMusicEles.push({
 			key: combineIndex,
@@ -336,6 +435,8 @@ export async function changeCombineAudio (combineIndex: number){
 	if(!state.accompany) {
 		state.noMusicSource = false
 	}
+	//设置进度
+	setAudioCurrentTime(currentTime)
 	showToast({
 		message:  "已开启原声",
 		position: "top",
@@ -381,8 +482,15 @@ export default defineComponent({
 		 * #11046
 		 * 声音与圆点消失的节点不一致,可能原因是部分安卓手机没有立即播放,所以需要等待有音频进度返回时再播放节拍器
 		 * mp3节拍器的圆点动画
+		 * 
+		 * #12291
+		 * 如果是选段评测,并且开始小节不是第一个小节,不需要播放节拍器的圆点动画
 		 */
 		const tickAnimate = (time: number) => {
+			if (state.section.length && state.section[0]?.MeasureNumberXML !== state.firstMeasureNumber) {
+				evaluatingData.needPlayTick = false;
+				return;
+			}
 			if (storeData.isApp && state.modeType === 'evaluating' && evaluatingData.needPlayTick && time > 0) {
 				evaluatingData.needPlayTick = false;
 				handleStartTick()
@@ -455,9 +563,9 @@ export default defineComponent({
 			if(state.isPreView){
 				state.isLoading = false;
 				return
-			}			
+			}
 			if (state.playMode !== "MIDI") {
-				console.time("音频加载时间123")
+				console.time("音频加载时")
 				// 处理音源
 				const [music, accompany, fanSong, banSong, mingSong, mingSongGirl] = await loadAudio()
 				audioData.backgroundEle = accompany;
@@ -501,7 +609,7 @@ export default defineComponent({
 				}
 				// 处理带节拍器的音源
 				//const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await loadBeatAudio()
-				// 客户端合成节拍器
+				/*// 客户端合成节拍器
 				const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await mergeBeatAudio(state.music, state.accompany)
 				Object.assign(audioData.songCollection, {
 					beatSongEle:beatMusic,
@@ -539,7 +647,7 @@ export default defineComponent({
 				if(beatMingSongGirl){
 					beatMingSongGirl.addEventListener("play", onPlay);
 					beatMingSongGirl.addEventListener("ended", onEnded);
-				}
+				} */
 				// 给男声女声赋值
 				const userGender = storeData.user.gender
 				// 当不为null 和undefined的时候 取userGender的值
@@ -550,8 +658,13 @@ export default defineComponent({
 				changeMingSongType()
 
 				state.audioDone = true;
-				state.isLoading = false
-				console.timeEnd("音频加载时间123")
+				// state.isLoading = false
+				console.timeEnd("音频加载耗时")
+				if (typeof console.timeLog === 'function') {
+					console.timeLog('加载过程','音频加载完成')
+				} else {
+					console.log('加载过程','音频加载完成')
+				}
 				console.log("音频数据:",audioData)
 				api_playProgress(progress);
 			} else {
@@ -564,6 +677,7 @@ export default defineComponent({
 				// 监听midi播放结束
 				api_cloudplayed(midiPlayEnd);
 			}
+			console.timeEnd('加载过程')
 		});
 		onUnmounted(() => {
 			api_remove_cloudplayed(midiPlayEnd);

+ 5 - 0
src/view/evaluating/evaluatResult.ts

@@ -54,6 +54,7 @@ const icons = [
 		mome: "敢于尝试",
 		clxImg: clx1,
 		clxtip: "你的演奏不太好,音准和完整性还需加强,再练一练吧~",
+		djytip: "你的演奏不太好,节奏还需加强,再练一练吧~",
 		clxmome: "敢于尝试"
 	},
 	{
@@ -62,6 +63,7 @@ const icons = [
 		mome: "还要加油哦~",
 		clxImg: clx2,
 		clxtip: "你的演奏还不熟练,音准和完整性还需加强,加紧训练才能有好成绩哦~",
+		djytip: "你的演奏还不熟练,节奏把握不太理想,加紧训练才能有好成绩哦~",
 		clxmome: "还要加油哦~"
 	},
 	{
@@ -70,6 +72,7 @@ const icons = [
 		mome: "突破自我",
 		clxImg: clx3,
 		clxtip: "你的演奏还不流畅,音准和节奏还需加强,科学的练习才能更完美哦~",
+		djytip: "你的演奏还不流畅,部分节奏需要勤加练习,科学的练习才能更完美哦~",
 		clxmome: "突破自我"
 	},
 	{
@@ -78,6 +81,7 @@ const icons = [
 		mome: "崭露头角",
 		clxImg: clx4,
 		clxtip: "你的演奏还不错,继续加油吧,加强音准,离完美就差一步啦~",
+		djytip: "你的演奏还不错,节奏还有些小瑕疵,离完美就差一步啦~",
 		clxmome: "崭露头角"
 	},
 	{
@@ -86,6 +90,7 @@ const icons = [
 		mome: "你很棒",
 		clxImg: clx5,
 		clxtip: "你的演奏非常不错,音准的把握和节奏稍有瑕疵,完整性把握的很好~",
+		djytip: "你的演奏非常不错,距离完成仅有一步之遥~",
 		clxmome: "你很棒"
 	},
 ];

+ 45 - 14
src/view/evaluating/index.tsx

@@ -34,6 +34,9 @@ import {
   api_cloudChangeSpeed,
   api_startDelayCheck,
   api_closeDelayCheck,
+  api_openCamera,
+  api_closeCamera,
+  api_recordAudioUpload
 } from "/src/helpers/communication";
 import state, { IPlayState, clearSelection, handleStopPlay, onPlay, resetPlaybackToStart, togglePlay, initSetPlayRate, resetBaseRate, scrollViewNote } from "/src/state";
 import { IPostMessage } from "/src/utils/native-message";
@@ -116,6 +119,7 @@ export const evaluatingData = reactive({
   tipErjiShow: false, // 评测提示弹窗
   onceErjiPopShow: false, // 是否已经提示过耳机弹窗,重新进入评测页面,重置该状态为false,手动关掉耳机弹窗,改变该状态为true,本次评测都不在提示耳机状态弹窗
   needCheckErjiStatus: true, // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
+  evaluatResultLoading: false, // 评测结果处理中
 });
 
 const sendOffsetTime = async (offsetTime: number) => {
@@ -310,7 +314,7 @@ export const addMeasureScore = (measureScore: any, show = true) => {
   // console.log("🚀 ~ measureScore:", evaluatingData.evaluatings)
 };
 
-const handleScoreResult = (res?: IPostMessage) => {
+const handleScoreResult = async (res?: IPostMessage) => {
   console.log("返回", res, evaluatingData.oneselfCancleEvaluating);
   // 如果是手动取消评测,不生成评测记录
   // if (evaluatingData.oneselfCancleEvaluating) {
@@ -344,6 +348,8 @@ const handleScoreResult = (res?: IPostMessage) => {
           evaluatingData.hideResultModal = true;
         }
         evaluatingData.resulstMode = evaluatingData.isErrorState ? false : true;
+        evaluatingData.startBegin = false;
+        evaluatingData.evaluatResultLoading = false;
       }, 200);
       evaluatingData.resultData = {
         ...body,
@@ -351,7 +357,8 @@ const handleScoreResult = (res?: IPostMessage) => {
       };
       // console.log("🚀 ~ evaluatingData.resultData:", evaluatingData.resultData)
       closeToast();
-      state.isLoading = false
+      // 评测记录接口调用后再取消评测打分提示loading
+      // state.isLoading = false
     }
   }
 };
@@ -395,15 +402,23 @@ export const handleStartBegin = async (preTimes?: number) => {
 		if (state.playState === "play" && (state.playType==="play"&&state.needTick)||(state.playType==="sing"&&state.needSingTick)) {
 			// 如果是系统节拍器 等系统节拍器播完了再播,如果是mp3节拍器 直接播
 			if((state.playType==="play" && !state.isOpenMetronome)||(state.playType==="sing" && !state.isSingOpenMetronome)){
-				const tickend = await handleStartTick();
-				console.log("🚀 ~ tickend:", tickend)
-				// 节拍器返回false, 取消播放
-				if (!tickend) {
-					state.playState = "paused";
-					evaluatingData.startBegin = false;
-					evaluatingData.isBeginMask = false
-					return;
-				}
+        /**
+        * #12291
+        * 如果是选段评测,并且开始小节不是第一个小节,不需要播放节拍器的圆点动画
+        */        
+        if (state.section.length && state.section[0]?.MeasureNumberXML !== state.firstMeasureNumber) {
+          // 
+        } else {
+          const tickend = await handleStartTick();
+          console.log("🚀 ~ tickend:", tickend)
+          // 节拍器返回false, 取消播放
+          if (!tickend) {
+            state.playState = "paused";
+            evaluatingData.startBegin = false;
+            evaluatingData.isBeginMask = false
+            return;
+          }
+        }
 			}else{
 				// handleStartTick()
         // 需要等待音频返回进度后再执行节拍器圆点动画
@@ -506,11 +521,12 @@ const recordStartTimePoint = async (res?: IPostMessage) => {
  * @returns
  */
 export const handleEndEvaluat = (isComplete = false, endType?: string) => {
-  // 没有开始评测 , 不是评测模式 , 不评分
-  if (!evaluatingData.startBegin || state.modeType !== "evaluating") return;
+  // 没有开始评测 , 不是评测模式 , 不评分;evaluatResultLoading:评测结果处理中,避免重复结束
+  if (!evaluatingData.startBegin || state.modeType !== "evaluating" || evaluatingData.evaluatResultLoading) return;
   // 结束录音
   // api_stopRecording();
   // 结束评测
+  evaluatingData.evaluatResultLoading = true
   console.log("评测结束1");
   endEvaluating({
     musicScoreId: state.examSongId,
@@ -533,7 +549,7 @@ export const handleEndEvaluat = (isComplete = false, endType?: string) => {
     }
   }
   setTimeout(() => {
-    evaluatingData.startBegin = false;
+    // evaluatingData.startBegin = false;
     if (endType === 'selfCancel') {
       // 重置播放倍率
       const item: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[0];
@@ -637,6 +653,7 @@ export const handleViewReport = (key: "recordId" | "recordIdStr", type: "gym" |
     statusBarTextColor: false,
     isOpenLight: true,
     c_orientation: 0,
+    showLoadingAnim: true
   });
 };
 
@@ -763,6 +780,18 @@ export default defineComponent({
       }
     };
 
+    // 打开摄像头
+    const openSetCamera = async () => {
+      if (state.setting.camera) {
+        const res = await api_openCamera();
+        // 没有授权
+        if (res?.content?.reson) {
+          state.setting.camera = false;
+          store.set("musicscoresetting", state.setting);
+        }
+      }
+    } 
+
     watch(pageVisibility, (value) => {
       if (value == "hidden" && evaluatingData.startBegin) {
         // handleEndBegin();
@@ -789,6 +818,7 @@ export default defineComponent({
       }
     );
     onMounted(() => {
+      openSetCamera();
       resetPlaybackToStart();
       hanlde_record();
       evaluatingData.resultData = {};
@@ -826,6 +856,7 @@ export default defineComponent({
       } else {
         removeSocketStatus(handleSocketStatus);
       }
+      // api_closeCamera();
       api_disconnectSocket();
       console.log("卸载评测模块成功");
     });

+ 2 - 0
src/view/fingering/fingering-config.ts

@@ -180,6 +180,7 @@ export const mappingVoicePart = (id: number | string, soruce: "GYM" | "COLEXIU"
       "Horn in F": 13,
       "Horn in F 1": 13,
       "Horn in F 2": 13,
+      "Horns in F": 13,
       "Trombone 1": 14,
       "Trombone 2": 14,
       "Trombone 3": 14,
@@ -292,6 +293,7 @@ export const matchVoicePart = (id: number | string, type: "SINGLE" | "CONCERT"):
       "Horn in F": 13,
       "Horn in F 1": 13,
       "Horn in F 2": 13,
+      "Horns in F": 13,
       "Trombone 1": 14,
       "Trombone 2": 14,
       "Trombone 3": 14,

+ 2 - 1
src/view/fingering/fingering-relationships.ts

@@ -325,7 +325,8 @@ const relationships = {
 		30: [1, 5, 6],
 		31: [4, 5, 3],
 		32: [4, 2, 3],
-		33: [4, 2, 6],
+		// 33: [4, 2, 6],
+		33: [1, 5, 3],
 		34: [1, 2, 3],
 		35: [4, 5, 6],
 		36: [4, 2, 6],

+ 27 - 6
src/view/music-score/index.tsx

@@ -1,5 +1,5 @@
 import { computed, defineComponent, onMounted, reactive, ref, onUnmounted } from "vue";
-import { formatXML, onlyVisible, getCustomInfo } from "../../helpers/formateMusic";
+import { formatXML, onlyVisible, getCustomInfo, onlyVisible2 } from "../../helpers/formateMusic";
 // // @ts-ignore
 import { OpenSheetMusicDisplay } from "/osmd-extended/src";
 import state, { EnumMusicRenderType, IPlatform, resetCursorPosition } from "/src/state";
@@ -35,6 +35,9 @@ export const resetRenderMusicScore = (type?: string) => {
 // 下载后的xml
 export const downloadXmlStr = ref("")
 
+// xml的document对象
+export const xmlDocRef = ref<Document | null>(null);
+
 export default defineComponent({
 	name: "music-score",
 	emits: ["rendered"],
@@ -59,6 +62,7 @@ export default defineComponent({
 		},
 	},
 	setup(props, { emit, slots, expose }) {
+		(window as any).DYhideTrackTune = false;
 		const query: any = getQuery();
 		let osmd: any = null;
 		/** 设置 曲谱模式,五线谱还是简谱 */
@@ -68,18 +72,35 @@ export default defineComponent({
 				state.musicRenderType = musicRenderType;
 			}
 		};
-		const getXML = async () => {
+		const getXML = async (cbType?: string) => {
 			// 当有下载的xml的时候直接使用,否则需要下载
 			if(!downloadXmlStr.value){
 				downloadXmlStr.value = await fetch(state.xmlUrl).then((response) => response.text())
 			}
+			console.time('增删改查xml')
+			// if (state.xmlFromStore) {
+			// 	musicData.score = state.isCombineRender ? downloadXmlStr.value : onlyVisible(downloadXmlStr.value, state.partIndex);
+			// 	if (state.gradualTimes) {
+			// 		state.gradual = getGradualLengthByXml(downloadXmlStr.value);
+			// 	}
+			// } else {
+			// 	const xmlStr = downloadXmlStr.value;
+			// 	const parseXmlInfo = getCustomInfo(xmlStr, cbType);
+			// 	const xml = formatXML(parseXmlInfo.parsedXML, '', cbType);
+			// 	musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
+			// 	if (state.gradualTimes) {
+			// 		state.gradual = getGradualLengthByXml(xml);
+			// 	}
+			// }
 			const xmlStr = downloadXmlStr.value;
-			const parseXmlInfo = getCustomInfo(xmlStr);
-			const xml = formatXML(parseXmlInfo.parsedXML);
-			musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
+			const parseXmlInfo = getCustomInfo(xmlStr, cbType);
+			const xml = formatXML(parseXmlInfo.parsedXML, '', cbType);
+			// musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
+			musicData.score = state.isCombineRender && state.combinePartIndexs.length === 0 ? xml : state.isCombineRender && state.combinePartIndexs.length > 1 ? onlyVisible2(xml, state.combinePartIndexs) : onlyVisible(xml, state.partIndex);
 			if (state.gradualTimes) {
 				state.gradual = getGradualLengthByXml(xml);
 			}
+			console.timeEnd('增删改查xml')
 		};
 
 		const init = async () => {
@@ -190,7 +211,7 @@ export default defineComponent({
 		let horizontalDragScroll:HorizontalDragScroll | null
 		onMounted(async () => {
 			//setRenderType();
-			await getXML();
+			await getXML('init');
 			await init();
 			// pc 端支持 拖动滚动
 			if(state.platform === "PC" || query.isCbs){

+ 1 - 1
src/view/plugins/toggleMusicSheet/choosePartName/index.tsx

@@ -77,7 +77,7 @@ export default defineComponent({
         <div class={styles.pickerCon}>
           <div class={styles.pickerBox}>
             {
-              state.isScoreRender &&
+              state.isScoreRender && state.modeType === "practise" && 
                 <>
                   {/* <div class={styles.titCon}>
                     <div class={styles.tit}>选择总谱</div>

+ 15 - 2
src/view/plugins/toggleMusicSheet/index.tsx

@@ -10,6 +10,8 @@ import useDrag from "/src/view/plugins/useDrag/index";
 import Dragbom from "/src/view/plugins/useDrag/dragbom";
 import { setGuidance } from "/src/page-instrument/custom-plugins/guide-page/api";
 import { storeData } from "/src/store";
+import { xmlDocRef, downloadXmlStr } from "/src/view/music-score"
+import IndexedDBService from "/src/utils/indexedDB";
 
 export const toggleMusicSheet = reactive({
   show: false,
@@ -51,7 +53,15 @@ export default defineComponent({
       }
     })
 
-    const switchMusic = (partIndexs: number[]) => {
+    // 切换的时候存储处理过后的xml
+    const storeXmlData = async () => {
+      const dbService = new IndexedDBService("MyDatabase", "MyStore");
+      await dbService.save({ id: state.examSongId, xmlString: downloadXmlStr.value })
+        .then(() => dbService.get(state.examSongId))
+        .then((data) => console.log("数据:", data));
+    }
+
+    const switchMusic = async (partIndexs: number[]) => {
       const index = partIndexs.join(",") as any
       // 暂停播放
       togglePlay("paused");
@@ -79,7 +89,10 @@ export default defineComponent({
           'part-index': index,
           'part-name': ''
         })
-      console.log(_url)
+      // const blob2 = new Blob([downloadXmlStr.value], { type: "text/html" });
+      // console.log(_url,xmlDocRef.value,downloadXmlStr.value)
+      state.evaluatAudioInitDone = false
+      await storeXmlData()
       location.href = _url
     }
 

+ 3 - 3
src/view/selection/index.module.less

@@ -83,9 +83,9 @@
 
 .scoreItem {
     position: absolute;
-    left: 80%;
-    top: -120%;
-    transform: translateX(-50%);
+    right: 10px;
+    top: -45px;
+    // transform: translateX(-50%);    
     font-size: 16px;
     font-family: "Roboto", sans-serif;
     font-weight: bold;

+ 17 - 5
src/view/selection/index.tsx

@@ -68,7 +68,7 @@ export default defineComponent({
 					if (item.svgElement) {
 						const noteEle = document.querySelector(`#vf-${item.svgElement?.attrs?.id}`);
 						if (noteEle) {
-							const noteBbox = noteEle.getBoundingClientRect?.() || { x: 0, width: 0 };
+							let noteBbox = noteEle.getBoundingClientRect?.() || { x: 0, width: 0 };
 							if (state.musicRenderType !== EnumMusicRenderType.staff) {
 								noteItem.bbox = {
 									left: noteBbox.x - parentLeft - noteBbox.width / 4 + "px",
@@ -92,11 +92,20 @@ export default defineComponent({
 								}
 								
 							} else {
+								// 判断是否是滑音,滑音的宽度很大,会覆盖掉前面的音符区域,导致无法点击选中前一个音符,需要缩小滑音的点击区域
+								let vibratoReduceX = 0;
+								if (noteEle?.querySelector('.vf-vibrato') && noteEle?.querySelector(".vf-note")) {
+									// vibratoReduceX = 50;
+									// @ts-ignore
+									noteBbox = noteEle.querySelector(".vf-note")?.getBoundingClientRect?.() || { x: 0, width: 0 };
+								} else {
+									vibratoReduceX = 0;
+								}							
 								const needTransY = -(staveBbox.height - customBgBox.height) / 2 + "px";
 								noteItem.bbox = {
-									left: noteBbox.x - parentLeft - noteBbox.width / 4 + "px",
+									left: noteBbox.x - parentLeft - noteBbox.width / 4 + vibratoReduceX + "px",
 									top: customBgBox.y ? customBgBox.y - parentTop + "px" : staveBbox.y - parentTop + "px",
-									width: noteBbox.width * 1.5 + "px",
+									width: noteBbox.width * 1.5 - vibratoReduceX + "px",
 									height: staveBbox.height + "px",
 									x: item.bbox?.x,
 									y: item.bbox?.y,
@@ -277,11 +286,14 @@ export default defineComponent({
 			}
 			return []
 		})
-
+		console.time('dom挂载')
 		onMounted(() => {
+			console.timeEnd('dom挂载')
 			selectData.notes = [];
 			selectData.staves = [];
+			console.time('添加dom时间')
 			calcNoteData();
+			console.timeEnd('添加dom时间')
 			const img: HTMLElement = document.querySelector('#cursorImg-0')!
 			if (metronomeData.cursorMode === 2){
 				img.classList.add('lineHide')
@@ -312,7 +324,7 @@ export default defineComponent({
 				>
 					{selectData.staves.map((item: any, index) => {
 						// 评测得分
-						const scoreItem = item.id && evaluatingData.evaluatings[item.measureListIndex];
+						const scoreItem = item && evaluatingData.evaluatings[item.measureListIndex];
 						// for(let idx in evaluatingData.evaluatings) {
 						// 	const { show, measureIndex } = evaluatingData.evaluatings[idx]
 						// 	if (show && measureIndex !== item.measureListIndex) {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
stats.html


+ 29 - 0
vite.config.ts

@@ -14,6 +14,16 @@ export default defineConfig({
   // assetsInclude: ['**/*.html'],
   plugins: [
     // mkcert(), // 本地https
+    // viteCompression({
+    //   algorithm: "gzip", // 指定压缩算法为gzip,[ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']
+    //   ext: ".gz", // 指定压缩后的文件扩展名为.gz
+    //   threshold: 10240, // 仅对文件大小大于threshold的文件进行压缩,默认为10KB
+    //   deleteOriginFile: false, // 是否删除原始文件,默认为false
+    //   filter: /\.(js|css|json|html|ico|svg)(\?.*)?$/i, // 匹配要压缩的文件的正则表达式,默认为匹配.js、.css、.json、.html、.ico和.svg文件
+    //   compressionOptions: { level: 9 }, // 指定gzip压缩级别,默认为9(最高级别)
+    //   verbose: true, //是否在控制台输出压缩结果
+    //   disable: false, //是否禁用插件
+    // }),
     legacy({
       targets: ["Chrome 63"],
       additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
@@ -35,11 +45,30 @@ export default defineConfig({
     },
   },
   build: {
+    minify: 'terser', // 启用 terser 压缩  
+    terserOptions: {  
+        compress: {  
+            // pure_funcs: ['console.log'], // 只删除 console.log 
+            //drop_console: true, // 删除所有 console
+            drop_debugger: true, // 删除 debugger  
+        }  
+    },
     rollupOptions: {
       input: {
         instrument: resolve(__dirname, "instrument.html"),
       },
       output: {
+        // 最小化拆分包
+        manualChunks(id) {
+          if (id.includes("osmd-extended")) {
+            // 通过拆分包的方式将所有来自osmd-extended的模块打包到单独的chunk中
+            return id
+              .toString()
+              .split("osmd-extended/")[1]
+              .split("/")[0]
+              .toString();
+          }
+        },
         chunkFileNames: "js/[name]-[hash].js", // 引入文件名的名称
         entryFileNames: "js/[name]-[hash].js", // 包的入口文件名称
         assetFileNames: "[ext]/[name]-[hash].[ext]", // 资源文件像 字体,图片等

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels