Browse Source

Merge branch 'feature-patch' into gym-dev

TIANYONG 3 tháng trước cách đây
mục cha
commit
9af6ab96bf
52 tập tin đã thay đổi với 1134 bổ sung658 xóa
  1. 71 1
      dist/instrument.html
  2. 1 0
      dist/js/index-10637826.js
  3. 0 0
      dist/js/index-5767ecf4.js
  4. 0 0
      dist/js/index-85c8a442.js
  5. 0 0
      dist/js/index-fe655f47.js
  6. 0 0
      dist/js/index-legacy-04a3baf9.js
  7. 1 0
      dist/js/index-legacy-5fa744d7.js
  8. 0 0
      dist/js/index-legacy-6fd536e0.js
  9. 0 0
      dist/js/index-legacy-c0a631c1.js
  10. 0 0
      dist/js/instrument-f5925bb3.js
  11. 0 0
      dist/js/instrument-legacy-6649239b.js
  12. 0 0
      dist/js/modeView-37c9707b.js
  13. 0 0
      dist/js/modeView-legacy-4a76c36d.js
  14. 0 0
      dist/js/node_modules-081fca9f.js
  15. 0 0
      dist/js/node_modules-legacy-cc3cd557.js
  16. 0 0
      dist/js/src-f076abb8.js
  17. 0 0
      dist/js/src-legacy-53d6b23f.js
  18. 61 1
      instrument.html
  19. 1 1
      osmd-extended
  20. 498 224
      src/constant/instruments.ts
  21. 1 1
      src/constant/instrumentsClassfiy.ts
  22. 6 0
      src/helpers/calcSpeed.ts
  23. 6 1
      src/helpers/communication.ts
  24. 0 25
      src/helpers/customMusicScore.ts
  25. 28 18
      src/helpers/formateMusic.ts
  26. 60 0
      src/hooks/errorLog/index.ts
  27. 42 0
      src/hooks/errorLog/uploadLog.ts
  28. 1 1
      src/page-instrument/App.tsx
  29. 1 0
      src/page-instrument/component/the-music-list/list.tsx
  30. 7 4
      src/page-instrument/evaluat-model/evaluat-result/index.tsx
  31. 42 12
      src/page-instrument/evaluat-model/index.tsx
  32. 1 1
      src/page-instrument/header-top/speed/index.tsx
  33. 19 1
      src/page-instrument/main.ts
  34. 2 3
      src/page-instrument/simple-detail/index.tsx
  35. 34 12
      src/page-instrument/view-detail/index.tsx
  36. 14 64
      src/page-instrument/view-evaluat-report/index.tsx
  37. 101 241
      src/state.ts
  38. 15 0
      src/utils/baseApi.ts
  39. 7 1
      src/utils/index.ts
  40. 1 1
      src/utils/request.ts
  41. 26 9
      src/view/audio-list/index.tsx
  42. 5 0
      src/view/evaluating/evaluatResult.ts
  43. 47 13
      src/view/evaluating/index.tsx
  44. 2 0
      src/view/fingering/fingering-config.ts
  45. 2 1
      src/view/fingering/fingering-relationships.ts
  46. 23 15
      src/view/music-score/index.tsx
  47. 1 1
      src/view/plugins/toggleMusicSheet/choosePartName/index.tsx
  48. 1 1
      src/view/plugins/toggleMusicSheet/index.tsx
  49. 3 3
      src/view/selection/index.module.less
  50. 1 1
      src/view/selection/index.tsx
  51. 1 0
      stats.html
  52. 1 1
      vite.config.ts

+ 71 - 1
dist/instrument.html

@@ -41,16 +41,82 @@
       })
     }
   </script>
+<<<<<<< HEAD
   <script type="module" crossorigin src="./js/instrument-30e9a353.js"></script>
   <link rel="modulepreload" crossorigin href="./js/node_modules-c5a1f0d3.js">
   <link rel="modulepreload" crossorigin href="./js/src-2c7b9553.js">
+=======
+  <script type="module" crossorigin src="./js/instrument-f5925bb3.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/node_modules-081fca9f.js">
+  <link rel="modulepreload" crossorigin href="./js/src-f076abb8.js">
+>>>>>>> feature-patch
   <link rel="stylesheet" href="./css/instrument-9723cd86.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>
     // 处理课堂乐器老师端打开听音练习时去掉加载动画
@@ -68,7 +134,11 @@
   </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-00a2b340.js"></script>
+<<<<<<< HEAD
   <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-aaed8169.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+=======
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-6649239b.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+>>>>>>> feature-patch
 </body>
 
 </html>

+ 1 - 0
dist/js/index-10637826.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-f5925bb3.js";import"./node_modules-081fca9f.js";import"./src-f076abb8.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};

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/index-5767ecf4.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/index-85c8a442.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/index-fe655f47.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/index-legacy-04a3baf9.js


+ 1 - 0
dist/js/index-legacy-5fa744d7.js

@@ -0,0 +1 @@
+System.register(["./instrument-legacy-6649239b.js","./node_modules-legacy-cc3cd557.js","./src-legacy-53d6b23f.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)])])}}))}}}));

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/index-legacy-6fd536e0.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/index-legacy-c0a631c1.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/instrument-f5925bb3.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/instrument-legacy-6649239b.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/modeView-37c9707b.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/modeView-legacy-4a76c36d.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/node_modules-081fca9f.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/node_modules-legacy-cc3cd557.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/src-f076abb8.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/src-legacy-53d6b23f.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 7c412c691b06b746fcedb430ed177f65e83d9474
+Subproject commit 6b139a79b2825dd9e4621336e29f5db52c771092

+ 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"],

+ 6 - 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,
 };
 
 /**

+ 6 - 1
src/helpers/communication.ts

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

+ 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
     }

+ 28 - 18
src/helpers/formateMusic.ts

@@ -247,14 +247,14 @@ export type CustomInfo = {
 };
 
 /** 从xml中获取自定义信息,并删除多余的字符串 */
-export const getCustomInfo = (xml: string): CustomInfo => {
+export const getCustomInfo = (xml: string, resourceType?: string): CustomInfo => {
 	const data = {
 		showSpeed: true,
 		parsedXML: xml,
 	};
 	//console.time('解析xml 耗时3')
 	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
-	const xmlParse = xmlDocRef.value ? xmlDocRef.value : 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) {
@@ -367,7 +367,7 @@ export const onlyVisible = (xml: string, partIndex: number, resourceType?: strin
 	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");
+	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() || "");
@@ -531,7 +531,6 @@ export const onlyVisible = (xml: string, partIndex: number, resourceType?: strin
 		});
 	}
 	// console.log(xmlParse)
-	
 	return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse));
 };
 
@@ -657,11 +656,11 @@ export const xmlAddPartName = (xml: string) => {
 /** 格式化曲谱
  * 1.全休止符的小节,没有音符默认加个全休止符
  */
-export const formatXML = (xml: string, xmlUrl?: string): string => {
+export const formatXML = (xml: string, xmlUrl?: string, resourceType?: string): string => {
 	if (!xml) return "";
 	//console.time('解析xml 耗时7')
 	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
-	const xmlParse = xmlDocRef.value ? xmlDocRef.value : 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");
@@ -752,6 +751,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) {
@@ -768,7 +768,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")) || [];
@@ -901,8 +902,12 @@ 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] || "";
+	/**
+	 * 当多选声部的时候 ,取选择的第一个声部
+	 * 总谱渲染时,需要取第一个渲染的声轨名字,canSelectTracks返回的声轨名字可能不是第一个,顺序有问题,需要用state.osmd.Sheet.Instruments
+	 */
+	// const firstTrackName = state.combinePartIndexs.length>1 ? state.partListNames[state.combinePartIndexs[0]] : state.canSelectTracks[0] || "";
+	const firstTrackName = state.combinePartIndexs.length>1 ? state.partListNames[state.combinePartIndexs[0]] : (state.osmd.Sheet.Instruments[0].Name || state.osmd.Sheet.Instruments[0].NameLabel.text || "");
 	const currentTrackIndex = state.isCombineRender && state.combinePartIndexs.length > 1 ? state.combinePartIndexs[0] : 0;
 	while (!iterator.EndReached) {
 		// console.log({ ...iterator });
@@ -1271,14 +1276,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);
@@ -1400,7 +1409,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				trackIndex: note.trackIndex, // 当前的音符属于第几条分轨
 				isStaccato: note.voiceEntry.isStaccato(),
 				isRestFlag: note.isRestFlag,
-				noteId: note.NoteToGraphicalNoteObjectId,
+				noteId: note.NoteToGraphicalNoteObjectId || `restNote${note.sourceMeasure.MeasureNumberXML}`,
+				// 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 {
+	//
+	}
+}

+ 1 - 1
src/page-instrument/App.tsx

@@ -146,7 +146,7 @@ export default defineComponent({
        */
       window.onload = function() {
         console.log('加载完成')
-        let timing: any = performance.getEntriesByType('navigation')[0];
+        let timing: any = performance.getEntriesByType('navigation')[0] || {};
         const { domainLookupEnd, domainLookupStart, connectEnd, connectStart, responseStart,
           requestStart, responseEnd, domInteractive, loadEventStart, domContentLoadedEventEnd,
           fetchStart, secureConnectionStart, transferSize, encodedBodySize,

+ 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 - 4
src/page-instrument/evaluat-model/evaluat-result/index.tsx

@@ -98,9 +98,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,6 +115,9 @@ export default defineComponent({
             }
           }, 0);
         }
+      },
+      {
+        immediate: true
       }
     );
     return () => (
@@ -166,7 +169,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 ? (

+ 42 - 12
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,13 +209,33 @@ 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)
+          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);
@@ -226,6 +248,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 +472,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();
@@ -626,14 +652,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>

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

@@ -14,7 +14,7 @@ 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 = () => {

+ 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(() => {

+ 34 - 12
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(() =>
@@ -129,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);
+        //   }
+        // }
       }
     });
 
@@ -177,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)派发计时器数据的时候触发
@@ -215,6 +219,22 @@ export default defineComponent({
       
       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;
@@ -224,8 +244,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);
@@ -348,6 +367,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)
@@ -360,6 +381,7 @@ export default defineComponent({
           );
         }
       }
+      // handleRendered(osmd)
     }
     /** 指法配置 */
     const fingerConfig = computed<any>(() => {

+ 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();

+ 101 - 241
src/state.ts

@@ -8,10 +8,10 @@ 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"
@@ -25,6 +25,8 @@ 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();
 
@@ -61,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",
@@ -571,7 +350,7 @@ const state = reactive({
   // 加载条
   isLoading: true,
   /** 加载中的文案 */
-  loadingText: '音频资源加载中,请稍后…',
+  loadingText: '资源加载中,请稍后…',
   /** 是否是简单的单行谱模式页面 */
   isSimplePage: false, 
   /** xml的速度和后台设置的速度,计算出的基础音频播放倍率 */
@@ -611,6 +390,10 @@ const state = reactive({
   isSingleMutliTrack: false,
   /** 是否是来源于缓存的xml */
   xmlFromStore: false,
+  /** 是否已经初始化评测音频,只有切了声轨后,才需要重新传音频,普通的切谱面(五线谱、简谱;单行谱、多行谱等)不需要重复传 */
+  evaluatAudioInitDone: false,
+  /** 是否使用原生评测服务 */
+  useNativeEvaluation: false,
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -750,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) {
@@ -838,6 +628,10 @@ 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);
     // 一行谱,点击音符,或者播放完成,需要跳转音符位置
@@ -867,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时候按  是否禁用节拍器  切换音源
@@ -1408,17 +1202,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",
+      behavior: animateType,
     });
   } else {
     musicScrollTop = 0
     musicAndSelection.scrollTo({
       top: 0,
-      behavior: "auto",
+      behavior: animateType,
     });
   }
 };
@@ -1451,12 +1246,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
 };
 
 
@@ -1469,6 +1269,19 @@ 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) => {
@@ -1490,6 +1303,11 @@ const queryMusicXml = async (id: string, xmlUr: string) => {
 }
 
 const getMusicInfo = async (res: any) => {
+  try {
+    await initInstrumentCode()
+  } catch (error) {
+    // console.log(error)
+  }
   // 是否支持总谱
   state.isScoreRender = res.data?.isScoreRender
   // 是否默认显示总谱
@@ -1501,7 +1319,7 @@ const getMusicInfo = async (res: any) => {
   /* 获取声轨列表 */
   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
@@ -1535,21 +1353,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);
@@ -1720,7 +1546,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,
@@ -1830,7 +1656,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 {
@@ -1895,7 +1724,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
@@ -2375,6 +2234,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());
+}
+

+ 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;
 		/** 
 		 * 只有后台才去设置

+ 26 - 9
src/view/audio-list/index.tsx

@@ -254,7 +254,7 @@ async function mergeBeatAudio(music?:string){
 			CrunkerInstance = new Crunker()
 		}
 		console.time("音频加载时间")
-		const [musicBuff, tickBuff, tockBuff] = await CrunkerInstance.fetchAudio(music?`${music}}`:undefined, tickMp3, tockMp3)
+		const [musicBuff, tickBuff, tockBuff] = await CrunkerInstance.fetchAudio(music?`${music}`:undefined, tickMp3, tockMp3)
 		console.timeEnd("音频加载时间")
 		// 计算音频空白时间
 		const silenceDuration = musicBuff&&!state.isEvxml ? CrunkerInstance.calculateSilenceDuration(musicBuff) : 0
@@ -321,7 +321,7 @@ export const handleLoadBeatMusic = async () => {
 	if(isBeatMusic || !currentMusic){
 		return
 	}
-	state.loadingText = "音频资源加载中,请稍后…"
+	state.loadingText = "资源加载中,请稍后…"
 	state.isLoading = true
 	const musicAudio = await mergeBeatAudio(currentMusic) as any
 	const playEleObj = {
@@ -369,19 +369,24 @@ export const handleLoadBeatMusic = async () => {
 
 // 切换对应的声轨,并且配置当前的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.isLoading = true;
+	state.loadingText = "资源加载中,请稍后…";
+	state.isLoading = true;
 	const musicUrl = audioData.combineMusics[combineIndex]
 	// 有就拿缓存,没有就加载
 	const cacheMusicIndex = audioData.combineMusicEles.findIndex(ele => {
@@ -398,15 +403,18 @@ export async function changeCombineAudio (combineIndex: number){
 		const music = await createAudio(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,
@@ -427,6 +435,8 @@ export async function changeCombineAudio (combineIndex: number){
 	if(!state.accompany) {
 		state.noMusicSource = false
 	}
+	//设置进度
+	setAudioCurrentTime(currentTime)
 	showToast({
 		message:  "已开启原声",
 		position: "top",
@@ -472,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()

+ 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: "你很棒"
 	},
 ];

+ 47 - 13
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) {
@@ -331,6 +335,10 @@ const handleScoreResult = (res?: IPostMessage) => {
     if (header?.commond === "overall") {
       console.log("🚀 ~ 评测返回:", res);
       console.log("评测结束", body);
+      // 如果评测结果没有返回音频,需要调用api通知APP端上传音频
+      if (!body.url) {
+        await api_recordAudioUpload()
+      }
       state.isHideEvaluatReportSaveBtn = false;
       setTimeout(() => {
         // 评测作业,如果不是完整评测,不展示评测弹窗
@@ -344,6 +352,8 @@ const handleScoreResult = (res?: IPostMessage) => {
           evaluatingData.hideResultModal = true;
         }
         evaluatingData.resulstMode = evaluatingData.isErrorState ? false : true;
+        evaluatingData.startBegin = false;
+        evaluatingData.evaluatResultLoading = false;
       }, 200);
       evaluatingData.resultData = {
         ...body,
@@ -395,15 +405,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 +524,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 +552,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 +656,7 @@ export const handleViewReport = (key: "recordId" | "recordIdStr", type: "gym" |
     statusBarTextColor: false,
     isOpenLight: true,
     c_orientation: 0,
+    showLoadingAnim: true
   });
 };
 
@@ -763,6 +783,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 +821,7 @@ export default defineComponent({
       }
     );
     onMounted(() => {
+      openSetCamera();
       resetPlaybackToStart();
       hanlde_record();
       evaluatingData.resultData = {};
@@ -826,6 +859,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],

+ 23 - 15
src/view/music-score/index.tsx

@@ -62,6 +62,7 @@ export default defineComponent({
 		},
 	},
 	setup(props, { emit, slots, expose }) {
+		(window as any).DYhideTrackTune = false;
 		const query: any = getQuery();
 		let osmd: any = null;
 		/** 设置 曲谱模式,五线谱还是简谱 */
@@ -71,25 +72,32 @@ 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);
-				const xml = formatXML(parseXmlInfo.parsedXML);
-				musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
-				if (state.gradualTimes) {
-					state.gradual = getGradualLengthByXml(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, cbType);
+			const xml = formatXML(parseXmlInfo.parsedXML, '', cbType);
+			musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
+			if (state.gradualTimes) {
+				state.gradual = getGradualLengthByXml(xml);
 			}
 			console.timeEnd('增删改查xml')
 		};
@@ -202,7 +210,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>

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

@@ -91,7 +91,7 @@ export default defineComponent({
         })
       // 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;

+ 1 - 1
src/view/selection/index.tsx

@@ -315,7 +315,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) {

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
stats.html


+ 1 - 1
vite.config.ts

@@ -48,7 +48,7 @@ export default defineConfig({
     minify: 'terser', // 启用 terser 压缩  
     terserOptions: {  
         compress: {  
-            pure_funcs: ['console.log'], // 只删除 console.log 
+            // pure_funcs: ['console.log'], // 只删除 console.log 
             //drop_console: true, // 删除所有 console
             drop_debugger: true, // 删除 debugger  
         }  

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