66 コミット 3ae57aebab ... 4c79dd76c8

作者 SHA1 メッセージ 日付
  TIANYONG 4c79dd76c8 feat: 指法匹配去掉数字和小数点 1 日 前
  TIANYONG 9fb659e7f0 feat: decode声轨名称 2 日 前
  TIANYONG baedd5899a feat: 支持传入part-name 1 週間 前
  TIANYONG 48fd418649 feat: 曲谱详情支持传入的zoom 1 週間 前
  TIANYONG 9f8fcbae7a feat: stringendo识别为渐快 1 週間 前
  TIANYONG 94ea2126fa feat: 合并休止小节兼容小节数从0开始的xml 1 週間 前
  TIANYONG 96f55224fe feat: 修改 2 週間 前
  TIANYONG 83919fba6d feat: 带有rit的xml,小节数从0开始时,异常处理 2 週間 前
  TIANYONG 5782c3f313 feat: "allargando", "molto allargando"识别为渐变 3 週間 前
  TIANYONG f109d52143 feat: 节拍指针播放去掉特色机型处理 3 週間 前
  TIANYONG 4f5d38ca9d Merge remote-tracking branch 'origin/hqyDev' into feature-tianyong 3 週間 前
  黄琪勇 9e8418e164 采样率修改 3 週間 前
  TIANYONG caa32bf4d9 feat: ios18,选段样式问题修复 3 週間 前
  TIANYONG 08df4d996b feat: ios18,选段样式问题修复 3 週間 前
  TIANYONG 39db4d5a40 feat: 播放晃动问题 3 週間 前
  TIANYONG ff750a6e5e feat: ios18,选段样式问题修复 3 週間 前
  TIANYONG aea7d5b7f0 build 3 週間 前
  TIANYONG 8bb1c626f5 Merge branch 'feature-tianyong' into gym-online 3 週間 前
  TIANYONG 8861cd3f88 feat: 多轨合并显示不支持合并休止小节 3 週間 前
  TIANYONG 76d8cd1e16 build 3 週間 前
  TIANYONG 5de41e881b feat: 切换声部关闭按钮 3 週間 前
  TIANYONG f489dff7c0 build 3 週間 前
  TIANYONG e8110253c3 style:样式修改 3 週間 前
  TIANYONG 48dd0bba7c style: 样式修改 3 週間 前
  TIANYONG 1a3bc4a420 Merge remote-tracking branch 'origin/hqy声部播放' into feature-tianyong 3 週間 前
  TIANYONG 7f10a5bcbd Merge branch 'gym-dev' into feature-tianyong 3 週間 前
  TIANYONG d17c5cf612 Merge branch 'feature-和弦' into feature-tianyong 3 週間 前
  TIANYONG 3a08d0e104 feat: 小数点计算精度问题 4 週間 前
  黄琪勇 9047e21d42 声轨 播放位置 调整 4 週間 前
  黄琪勇 62b70210fd 声轨 按钮修改 4 週間 前
  黄琪勇 b6db5edcb0 多声轨渲染时候,可以根据选择的声轨 播放对应的原音 1 ヶ月 前
  黄琪勇 8115462125 Merge branch 'feature-tianyong' of http://git.dayaedu.com/tianyong/gym-music-score into hqy声部播放 1 ヶ月 前
  TIANYONG 6375b98048 feat: 声轨名称添加自定义属性 1 ヶ月 前
  TIANYONG c140ef86ac feat: 设置里增加合并休止小节设置 1 ヶ月 前
  TIANYONG 03ccac40ae style: 声轨弹窗大小修改 1 ヶ月 前
  TIANYONG 1ee32c69b5 feat: 和弦评测修改 1 ヶ月 前
  黄琪勇 5d868cbe0d Merge branch 'gym-online' of http://git.dayaedu.com/tianyong/gym-music-score into hqyDev 1 ヶ月 前
  黄琪勇 5e838db731 Merge branch 'feature-tianyong' of http://git.dayaedu.com/tianyong/gym-music-score into hqyDev 1 ヶ月 前
  TIANYONG 47182dc6bd feat: 和弦支持测评功能 1 ヶ月 前
  TIANYONG e6d4c139a4 feat: 声轨切换增加重置按钮 1 ヶ月 前
  TIANYONG 6f9949c3c0 feat: 谱面编辑渐强渐弱线优化 1 ヶ月 前
  TIANYONG 11a808974d build 1 ヶ月 前
  TIANYONG 4fa83f20b2 build 1 ヶ月 前
  TIANYONG c62dbea805 feat: 修改 1 ヶ月 前
  TIANYONG a120064b39 fest: 谱面编辑回显修改 1 ヶ月 前
  TIANYONG 02c4dcdeb2 feat: 谱面编辑渐强渐弱线优化 1 ヶ月 前
  TIANYONG 4e17cc7873 feat: 声轨名称带空格处理 1 ヶ月 前
  TIANYONG d7a3c8fd54 feat: 速度问题修改 1 ヶ月 前
  TIANYONG 342c198116 feat: 修改下载的图片页码位置 1 ヶ月 前
  TIANYONG 327a859ec6 feat: 速度问题修改 1 ヶ月 前
  TIANYONG 6dde2175c9 feat: 声轨名称带空格处理 1 ヶ月 前
  TIANYONG 32b84009c3 feat: 下载修改 1 ヶ月 前
  黄琪勇 a3a354ffa1 速度弹窗 样式修改 1 ヶ月 前
  TIANYONG 52e484522b style: 谱面缩放,指针大小跟随变化 1 ヶ月 前
  TIANYONG f875a192e7 build 1 ヶ月 前
  TIANYONG c84db733bc Merge branch 'hotfix-11-11' into gym-online 1 ヶ月 前
  黄琪勇 6e7dd98b12 Merge branch 'hqy会员' of http://git.dayaedu.com/tianyong/gym-music-score into gym-online 1 ヶ月 前
  TIANYONG 7493d9ff5e build 1 ヶ月 前
  黄琪勇 fbd25cf9cf 动画修改 1 ヶ月 前
  黄琪勇 332fe66642 动画修改 1 ヶ月 前
  TIANYONG 678bea990b feat: 评测作业进入云教练,非会员先不进行延迟检测 1 ヶ月 前
  黄琪勇 e0f34176e1 弹窗和引导页同时出现的兼容 1 ヶ月 前
  黄琪勇 15e4826131 build 1 ヶ月 前
  黄琪勇 b31f87aa82 会员隐藏逻辑 1 ヶ月 前
  黄琪勇 e8912a1f47 文案修改 1 ヶ月 前
  黄琪勇 361a2b5f39 会员新逻辑 1 ヶ月 前
67 ファイル変更627 行追加271 行削除
  1. 0 0
      dist/css/instrument-f4164af0.css
  2. 5 5
      dist/instrument.html
  3. 1 1
      dist/js/index-9669d689.js
  4. 0 0
      dist/js/index-b2d2ff09.js
  5. 0 0
      dist/js/index-b3f2f534.js
  6. 0 0
      dist/js/index-c9a7b7cc.js
  7. 0 0
      dist/js/index-legacy-4a016637.js
  8. 1 1
      dist/js/index-legacy-5f13d845.js
  9. 0 0
      dist/js/index-legacy-94b8f6ac.js
  10. 0 0
      dist/js/index-legacy-b626ab6b.js
  11. 0 0
      dist/js/instrument-2e311432.js
  12. 0 0
      dist/js/instrument-f8048548.js
  13. 0 0
      dist/js/instrument-legacy-be053aad.js
  14. 0 1
      dist/js/modeView-29103ac0.js
  15. 0 0
      dist/js/modeView-6002f238.js
  16. 0 0
      dist/js/modeView-legacy-5b8f0141.js
  17. 0 1
      dist/js/modeView-legacy-8b48b21f.js
  18. 0 0
      dist/js/polyfills-0f8dda44.js
  19. 0 0
      dist/js/polyfills-7a7509f3.js
  20. 0 0
      dist/js/polyfills-legacy-7329b624.js
  21. 0 0
      dist/js/polyfills-legacy-7ea27e1e.js
  22. BIN
      dist/png/cancelBtn-3ab99c9b.png
  23. BIN
      dist/png/resetBtn-4f6672f5.png
  24. BIN
      dist/png/tips-02a6a659.png
  25. 1 1
      osmd-extended
  26. 3 1
      src/helpers/calcSpeed.ts
  27. 16 4
      src/helpers/formateMusic.ts
  28. 1 4
      src/helpers/metronome.ts
  29. 2 2
      src/helpers/svgToPng.ts
  30. 6 2
      src/page-instrument/api.ts
  31. BIN
      src/page-instrument/component/the-music-list/imgs/lock.png
  32. 35 12
      src/page-instrument/component/the-music-list/index.module.less
  33. 23 14
      src/page-instrument/component/the-music-list/list.tsx
  34. 58 32
      src/page-instrument/component/vip/index.module.less
  35. 21 19
      src/page-instrument/component/vip/index.tsx
  36. BIN
      src/page-instrument/component/vip/tips.png
  37. 3 2
      src/page-instrument/evaluat-model/index.tsx
  38. 0 0
      src/page-instrument/header-top/image/glMode.json
  39. 0 0
      src/page-instrument/header-top/image/lxMode.json
  40. 0 0
      src/page-instrument/header-top/image/pcMode.json
  41. 1 1
      src/page-instrument/header-top/index.module.less
  42. 8 1
      src/page-instrument/header-top/index.tsx
  43. 39 39
      src/page-instrument/header-top/modeView.tsx
  44. 6 0
      src/page-instrument/header-top/settting/index.module.less
  45. 18 2
      src/page-instrument/header-top/settting/index.tsx
  46. 8 5
      src/page-instrument/header-top/speed/index.module.less
  47. 2 1
      src/page-instrument/header-top/speed/index.tsx
  48. 11 9
      src/page-instrument/view-detail/index.tsx
  49. 53 11
      src/state.ts
  50. 0 2
      src/store.ts
  51. 1 1
      src/utils/crunker.ts
  52. 141 69
      src/view/audio-list/index.tsx
  53. 2 2
      src/view/fingering/fingering-config.ts
  54. BIN
      src/view/music-score/combineAudio/imgs/lock.png
  55. BIN
      src/view/music-score/combineAudio/imgs/open.png
  56. 18 0
      src/view/music-score/combineAudio/index.module.less
  57. 68 0
      src/view/music-score/combineAudio/index.tsx
  58. 8 5
      src/view/music-score/index.tsx
  59. 23 3
      src/view/plugins/move-music-score/index.tsx
  60. BIN
      src/view/plugins/toggleMusicSheet/choosePartName/imgs/resetBtn.png
  61. 11 7
      src/view/plugins/toggleMusicSheet/choosePartName/index.module.less
  62. 19 6
      src/view/plugins/toggleMusicSheet/choosePartName/index.tsx
  63. 1 0
      src/view/plugins/toggleMusicSheet/index.tsx
  64. 4 4
      src/view/selection/index.module.less
  65. 8 0
      src/view/selection/index.tsx
  66. 0 0
      stats.html
  67. 1 1
      vite.config.ts

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/css/instrument-f4164af0.css


+ 5 - 5
dist/instrument.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-7a7509f3.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-0f8dda44.js"></script>
 
   <meta charset="UTF-8" />
   <meta name="viewport"
@@ -41,8 +41,8 @@
       })
     }
   </script>
-  <script type="module" crossorigin src="./js/instrument-2e311432.js"></script>
-  <link rel="stylesheet" href="./css/instrument-779663c4.css">
+  <script type="module" crossorigin src="./js/instrument-f8048548.js"></script>
+  <link rel="stylesheet" href="./css/instrument-f4164af0.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>
@@ -65,8 +65,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-7329b624.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-2487cf7d.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</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-be053aad.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 1 - 1
dist/js/index-2b3cdad1.js → dist/js/index-9669d689.js

@@ -1 +1 @@
-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-2e311432.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};
+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-f8048548.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};

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/index-b2d2ff09.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/index-b3f2f534.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/index-c9a7b7cc.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/index-legacy-4a016637.js


+ 1 - 1
dist/js/index-legacy-0fc270ac.js → dist/js/index-legacy-5f13d845.js

@@ -1 +1 @@
-System.register(["./instrument-legacy-2487cf7d.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)])])}}))}}}));
+System.register(["./instrument-legacy-be053aad.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)])])}}))}}}));

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/index-legacy-94b8f6ac.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/index-legacy-b626ab6b.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/instrument-2e311432.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/instrument-f8048548.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/instrument-legacy-be053aad.js


+ 0 - 1
dist/js/modeView-29103ac0.js

@@ -1 +0,0 @@
-import{d as c,K as r,a1 as i,q as m,b as o,a2 as u,a3 as a,s as e,a4 as t,a5 as s,a6 as g,a7 as h,a8 as p}from"./instrument-2e311432.js";const v=c({name:"modeView",setup(){var l;r(()=>i.socketErrorStatus,()=>{i.socketErrorStatus===2&&setTimeout(()=>{i.socketErrorPop=!1},1e3)});const n=m(),d=((l=navigator==null?void 0:navigator.userAgent)==null?void 0:l.includes("UAWEIVRD-W09"))||(n==null?void 0:n.iPad)||n.isTablet;return()=>o("div",{class:[a.modeView,d&&a.isiPad,s.modeType!=="init"&&a.hidden]},[o("img",{src:u,class:a.back,onClick:()=>{e.isSingleLine&&(t.isShow.value=e.melodyLine),s.oldModeType!=="practise"&&(i.needCheckErjiStatus=!1,s.handleChangeModeType(s.oldModeType)),s.modeType="show"}},null),o("div",{class:[a.modeBox,(!e.isPercussion&&!e.enableEvaluation||e.isPercussion&&e.enableEvaluation||e.isPercussion&&!e.enableEvaluation)&&a.twoModeBox]},[o("img",{class:a.modeImg,src:g,onClick:()=>{e.isSingleLine&&(t.isShow.value=e.melodyLine),s.handleChangeModeType("practise")}},null),!e.isPercussion&&o("img",{class:a.modeImg,src:h,onClick:()=>s.handleChangeModeType("follow")},null),e.enableEvaluation&&o("img",{class:a.modeImg,src:p,onClick:()=>{i.needCheckErjiStatus=!0,s.handleChangeModeType("evaluating")}},null)])])}});export{v as default};

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/modeView-6002f238.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/modeView-legacy-5b8f0141.js


+ 0 - 1
dist/js/modeView-legacy-8b48b21f.js

@@ -1 +0,0 @@
-System.register(["./instrument-legacy-2487cf7d.js"],(function(e,i){"use strict";var s,a,n,o,l,t,d,r,u,c,m,g,v;return{setters:[e=>{s=e.d,a=e.K,n=e.a1,o=e.q,l=e.b,t=e.a2,d=e.a3,r=e.s,u=e.a4,c=e.a5,m=e.a6,g=e.a7,v=e.a8}],execute:function(){e("default",s({name:"modeView",setup(){var e,i;a((()=>n.socketErrorStatus),(()=>{2===n.socketErrorStatus&&setTimeout((()=>{n.socketErrorPop=!1}),1e3)}));const s=o(),h=(null===(e=navigator)||void 0===e||null===(i=e.userAgent)||void 0===i?void 0:i.includes("UAWEIVRD-W09"))||(null==s?void 0:s.iPad)||s.isTablet;return()=>l("div",{class:[d.modeView,h&&d.isiPad,"init"!==c.modeType&&d.hidden]},[l("img",{src:t,class:d.back,onClick:()=>{r.isSingleLine&&(u.isShow.value=r.melodyLine),"practise"!==c.oldModeType&&(n.needCheckErjiStatus=!1,c.handleChangeModeType(c.oldModeType)),c.modeType="show"}},null),l("div",{class:[d.modeBox,(!r.isPercussion&&!r.enableEvaluation||r.isPercussion&&r.enableEvaluation||r.isPercussion&&!r.enableEvaluation)&&d.twoModeBox]},[l("img",{class:d.modeImg,src:m,onClick:()=>{r.isSingleLine&&(u.isShow.value=r.melodyLine),c.handleChangeModeType("practise")}},null),!r.isPercussion&&l("img",{class:d.modeImg,src:g,onClick:()=>c.handleChangeModeType("follow")},null),r.enableEvaluation&&l("img",{class:d.modeImg,src:v,onClick:()=>{n.needCheckErjiStatus=!0,c.handleChangeModeType("evaluating")}},null)])])}}))}}}));

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/polyfills-0f8dda44.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/polyfills-7a7509f3.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/polyfills-legacy-7329b624.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/js/polyfills-legacy-7ea27e1e.js


BIN
dist/png/cancelBtn-3ab99c9b.png


BIN
dist/png/resetBtn-4f6672f5.png


BIN
dist/png/tips-02a6a659.png


+ 1 - 1
osmd-extended

@@ -1 +1 @@
-Subproject commit 46bdba03a3654e74d0ad2bd61e993a2467d0f80e
+Subproject commit a9750e2d69cdef62c365035e86f131586a25aa69

+ 3 - 1
src/helpers/calcSpeed.ts

@@ -41,6 +41,8 @@ export const speedInfo: { [key in string]: number } = {
 	slow: 1.333333333,
 	slowly: 1.333333333,
 	faster: 1.333333333,
+	"molto allargando": 1.333333333,
+	stringendo: 0.8,
 };
 
 /**
@@ -184,7 +186,7 @@ export const getGradualLengthByXml = (xml: string) => {
 		}
 		const isKeyWork = keys.find((k) => {
 			const ks = k.split(" ");
-			return textContent && ks.includes(textContent);
+			return textContent && ks.includes(textContent) || k === textContent;
 		});
 		if (ele.type === "metronome" || (ele.type === "words" && (textContent.startsWith("a tempo") || isKeyWork)) || isLastNoteAndNotClosed) {
 			const indexOf = gradualNotes.findIndex((item) => item.length === 1);

+ 16 - 4
src/helpers/formateMusic.ts

@@ -656,6 +656,7 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 	}
 
 	const measures = Array.from(xmlParse.getElementsByTagName("measure"));
+	state.firstMeasureNumber = measures[0] ? Number(measures[0].getAttribute('number') || 1) : 1;
 	const minutes: any = xmlParse.getElementsByTagName("per-minute");
 	let speeds: any = []
 	for (const minute of minutes) {
@@ -859,7 +860,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 
 	let preNoteEndTime = 0; // 上一个音符的结束时间
 
-	let preNoteMeasureNumber = 0; // 上一个小节的number值
+	let preNoteMeasureNumber: any = null; // 上一个小节的number值
 
 	let currentRealTempo: any = {}; // 当前小节的速度与拍号信息
 
@@ -883,7 +884,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 		if (state.isCombineRender) {
 			iterator.currentVoiceEntries = iterator.currentVoiceEntries.filter((item: any) => {
 				const trackName = state.isEvxml && state.evxmlAddPartName ? item.parentVoice.parent.IdString || '' : item.parentVoice.parent.Name || '';
-				return trackName === firstTrackName
+				return trackName?.trim() === firstTrackName
 			});
 		}
 		let minIndex = 0, elRealValue = 0
@@ -1060,7 +1061,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			
 			// 合并展示某些分轨,需要把展示的分轨筛选出来
 			if (state.isCombineRender && note.sourceMeasure.verticalMeasureList.length) {
-				note.sourceMeasure.verticalMeasureList = note.sourceMeasure?.verticalMeasureList.filter((item: any) => state.canSelectTracks.includes(item?.parentStaff?.parentInstrument.Name))
+				note.sourceMeasure.verticalMeasureList = note.sourceMeasure?.verticalMeasureList.filter((item: any) => state.canSelectTracks.includes(item?.parentStaff?.parentInstrument.Name?.trim()))
 			}
 
 			activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[currentTrackIndex]] || [];
@@ -1418,7 +1419,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				xmlMp3BeatFixTime,  //xml上节拍器的时间
 				notBeatFixtime: state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime, // 不含节拍器的fixtime值 唱名用
 				notBeatTime: state.isEvxml && evNoteStartTime ? retain(evNoteStartTime) : retain(relativeTime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)), // 不含节拍器的 音符开始时间
-				notBeatEndTime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)) // 不含节拍器的 音符结束时间
+				notBeatEndTime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)), // 不含节拍器的 音符结束时间
+				frequencyList: [note?.pitch?.frequency || -1], // 如果是和弦音符,需要添加多个音符的频率,用于评测
 			};
 			// console.log(i,'当前的小节',nodeDetail.MeasureNumberXML,totalMultipleRestMeasures,multipleRestMeasures)
 			// 如果是妙极客的曲子,并且第二遍循环播放需要等待时间,并且是第二遍循环的第一个小节的第一个音符
@@ -1429,6 +1431,16 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// 	relativeTime = relativeTime + state.secondEvXmlBeginTime;
 			// }
 			// if (state.isEvxml && nodeDetail.repeatIdx && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
+
+			// 如果是和弦音符,需要添加多个音符的频率,用于评测
+			if (note.voiceEntry.notes.length > 1) {
+				note.voiceEntry.notes.forEach((cnote: any) => {
+					if (cnote?.IsChordNote && cnote?.pitch?.frequency) {
+						nodeDetail.frequencyList.push(cnote.pitch.frequency)
+					}
+				})
+			}
+
 			const firstRepeatNodeId = allNotes.find((item: any) => item.MeasureNumberXML === state.timegapRepeatMeasureIndex)?.noteId || 0;
 			if (state.isEvxml && nodeDetail.repeatIdx && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === state.timegapRepeatMeasureIndex && nodeDetail.noteId === firstRepeatNodeId) {
 				const currentWaitTime = state.evXmlBeginArr[nodeDetail.repeatIdx] || 0;

+ 1 - 4
src/helpers/metronome.ts

@@ -151,9 +151,6 @@ class Metronome {
 
 	// 播放
 	sound = (currentTime: number) => {
-		if (!state.sectionStatus){
-			currentTime = setCurrentTime(currentTime);
-		}
 		let index = -1;
 		let activeMetro = -1;
 		for (let i = 0; i < metronomeData.metroList.length; i++) {
@@ -323,7 +320,7 @@ class Metronome {
 			// console.log("🚀 ~ note?.noteElement?.sourceMeasure", note?.noteElement?.sourceMeasure)
 			// console.log("🚀 ~ measureNumberXML", measureNumberXML, note)
 			// console.log("🚀 ~ measureNumberXML", note)
-			const measureListIndex = measureNumberXML - 1;
+			const measureListIndex = state.firstMeasureNumber == 0 ? measureNumberXML : measureNumberXML - 1;
 			// 当渐快渐慢的时候  不播节拍器
 			if(isWithinRange(state.gradual, measureListIndex)){
 				xmlNumber = measureNumberXML;

+ 2 - 2
src/helpers/svgToPng.ts

@@ -9,8 +9,8 @@ export const getSvgPngToSize = (osmd: any) => {
           var state = backend.ctx.state;
           var width = backend.ctx.width / state.scale.x;
           var height = backend.ctx.height / state.scale.y;
-          const textX = width - 120,textY = height - 50;
-          const textDom = `<g><text x="${textX}" y="${textY}" stroke-width="3" fill="#000000" stroke="none" stroke-dasharray="none" font-family="Times New Roman" font-size="36px" font-weight="bold" font-style="none">第${idx+1}页</text></g>`
+          const textX = width - 90,textY = height - 90;
+          const textDom = `<g><text x="${textX}" y="${textY}" stroke-width="3" fill="#000000" stroke="none" stroke-dasharray="none" font-family="Times New Roman" font-size="36px" font-weight="bold" font-style="none">${idx+1}/${len}</text></g>`
           backend.ctx.svg.innerHTML = backend.ctx.svg.innerHTML + textDom;
           var cont = new XMLSerializer().serializeToString(
             backend.ctx.svg

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

@@ -48,8 +48,12 @@ export const studentQueryUserInfo = async () => {
         phone:data.phone,
         clientType:"STUDENT",
         id:data.id,
-        gender:"",
-        membershipEndTime: data.membershipEndTime
+        gender:""
+      }
+      if (res.data.specialInstrumentIds.length > 1 && data.extInstrumentNames?.length) {
+        data.extInstrumentNames.forEach((ext: any) => {
+          res.data.specialInstrumentIds.push(ext.instrumentIds)
+        })
       }
       return res
     }

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


+ 35 - 12
src/page-instrument/component/the-music-list/index.module.less

@@ -147,17 +147,22 @@
         overflow: hidden;
         position: relative;
         flex-shrink: 0;
-        .iconType {
+        .lock{
             position: absolute;
-            width: 28px;
-            height: 14px;
-            right: 0;
-            top: 0;
-            z-index: 9;
-            border-top-right-radius: 8px !important;
-            &.VIP {
-                background: url('./imgs/icon-music-vip.png') no-repeat center;
-                background-size: contain;
+            width: 100%;
+            height: 100%;
+            background-color: rgba(0,0,0,.3);
+            z-index: 1;
+            &::after{
+                content: "";
+                position: absolute;
+                top: 50%;
+                left: 50%;
+                transform: translate(-50%, -50%);
+                width: 13px;
+                height: 15px;
+                background: url("./imgs/lock.png") no-repeat;
+                background-size: 100% 100%;
             }
         }
     }
@@ -177,7 +182,6 @@
             display: flex;
             align-items: center;
             margin-top: 6px;
-            height: 14px;
             .usedNum{
                 display: flex;
                 align-items: center;
@@ -199,15 +203,34 @@
                 }
             }
             .author {
+                margin-left: 5px;
                 font-weight: 400;
                 font-size: 13px;
                 color: rgba(0,0,0,0.5);
-                line-height: 1;
+                line-height: 14px;
                 flex-grow: 1;
                 white-space: nowrap;
                 overflow: hidden;
                 text-overflow: ellipsis;
             }
+            .freeIcon{
+                padding: 0px 3px;
+                border-radius: 3px;
+                font-weight: 400;
+                font-size: 10px;
+                color: #01C1B5;
+                line-height: 14px;
+                border: 1px solid rgba(1,193,181,0.7);
+            }
+            .lockIcon{
+                padding: 0px 3px;
+                border-radius: 3px;
+                font-weight: 400;
+                font-size: 10px;
+                color: #D08C24;
+                line-height: 14px;
+                border: 1px solid rgba(208,140,36,0.7);
+            }
         }
     }
 }

+ 23 - 14
src/page-instrument/component/the-music-list/list.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, onMounted, reactive, watch } from "vue";
+import { computed, defineComponent, onMounted, reactive, watch } from "vue";
 import styles from "./index.module.less";
 import { api_musicSheetPage } from "../../api";
 import state, { togglePlay } from "/src/state";
@@ -9,7 +9,7 @@ import searImg from "./imgs/searImg.png"
 import huoimg from "./imgs/huo.png"
 import emptyImg from "./imgs/empty.png"
 import { getQuery } from "/src/utils/queryString";
-import { vipData, isVip } from "../vip"
+import { vipData } from "../vip"
 
 export default defineComponent({
   name: "TheMusicList-list",
@@ -75,13 +75,8 @@ export default defineComponent({
     });
 
     const openAccomapina = (item: any) => {
-      // 学生端 没有开会员查看会员曲目
-      if(item.paymentType === "VIP" && state.systemType === "student" && !isVip.value){
-        vipData.show = true
-        return
-      }
-      // 学校查看会员曲目
-      if(item.paymentType === "VIP" && state.systemType === "web" && state.isSchool){
+      // 学生端 没有开会员查看会员曲目    // 学校查看会员曲目
+      if(item.useStatus === "LOCK" && vipVerify.value){
         vipData.show = true
         return
       }
@@ -100,6 +95,7 @@ export default defineComponent({
       }
       queryObj.id = item.id
       queryObj["part-index"] = ""
+      queryObj["part-name"] = ""
       location.href =
         location.origin +
         location.pathname +
@@ -111,6 +107,9 @@ export default defineComponent({
           ? (num / 10000).toFixed(1).replace(/\.0$/, '') + "万" 
           : num.toString();
     }
+    const vipVerify = computed(() => {
+      return state.systemType === "student" || (state.systemType === "web" && state.isSchool)
+    })
     return () => (
       <div class={styles.wrap}>
         <div class={[styles.searchBox,data.isFocus && styles.isFocus]}>
@@ -131,16 +130,26 @@ export default defineComponent({
             return (
               <div class={[styles.item, state.examSongId == item.id && styles.itemActive]} onClick={() => openAccomapina(item)}>
                 <div class={styles.titleImg}>
-                  <i class={[styles.iconType, styles[item.paymentType]]}></i>
+                  {
+                    item.useStatus==='LOCK'&&vipVerify.value && <div class={styles.lock}></div>
+                  }
                   <Image src={item.titleImg} class={styles.img} />
                 </div>
                 <div class={styles.content}>
-                  <p class={styles.name}>{item.musicSheetName}</p>
-                  {
-                    item.composer &&                   
+                  <div class={styles.name}>{item.musicSheetName}</div>
+                  {          
+                    ((["FREE","LOCK"].includes(item.useStatus)&&vipVerify.value) || item.composer) &&
                     <div class={styles.detail}>
                       {/* {item.usedNum && <div class={styles.usedNum}><img src={huoimg}/><div>{formatNumber(item.usedNum)}</div></div>} */}
-                      <p class={styles.author}>{item.composer}</p>
+                      {
+                        item.useStatus==='FREE'&&vipVerify.value && <div class={[styles.freeIcon]}>试用</div>
+                      }
+                      {
+                        item.useStatus==='LOCK'&&vipVerify.value && <div class={[styles.lockIcon]}>未解锁</div>
+                      }
+                      {
+                        item.composer && <div class={styles.author}>{item.composer}</div>
+                      }
                     </div>
                   }
                 </div>

+ 58 - 32
src/page-instrument/component/vip/index.module.less

@@ -1,33 +1,59 @@
-.vip{
-  padding: 20px 30px;
-  padding-top: 30px;
-  min-width: 230px;
-  >img{
-    width: 161px;
-    margin: 0 auto 20px;
-    display: block;
-  }
-  >p{
-    margin: 0;
-    font-size: 14px;
-    color: #808080;
-    margin: 0 auto 20px;
-    text-align: center;
-  }
-  .btn{
-    font-size: 16px;
-    height: 40px;
-    line-height: 40px;
-    width: 100%;
-  }
-  &+i{
-    right: 10px;
-    top: 10px;
-  }
+.vipPopup {
+   /* 引导页出来不能点击的问题 */
+   pointer-events: initial !important;
+   & * {
+      pointer-events: initial !important;
+   }
+   :global {
+      .van-popup__close-icon {
+         color: #333333;
+         top: 23px;
+         right: 23px;
+         font-size: 20px;
+      }
+   }
+   .vip {
+      padding-bottom: 20px;
+      width: 295px;
+      .title {
+         height: 49px;
+         background: linear-gradient(to bottom, #defaff, #ffffff);
+         padding-bottom: 4px;
+         display: flex;
+         justify-content: center;
+         align-items: flex-end;
+         .tit {
+            font-weight: 500;
+            font-size: 18px;
+            color: #1a1a1a;
+            line-height: 25px;
+         }
+      }
+      .text {
+         margin-top: 16px;
+         padding: 0 20px;
+         font-weight: 400;
+         font-size: 16px;
+         color: #666666;
+         line-height: 26px;
+         text-align: center;
+      }
+      .btnCon {
+         margin-top: 25px;
+         display: flex;
+         justify-content: center;
+         :global {
+            .van-button {
+               width: 120px;
+               height: 40px;
+               border-radius: 20px;
+               font-weight: 500;
+               font-size: 16px;
+               & + .van-button {
+                  margin-left: 15px;
+               }
+            }
+         }
+      }
+   }
 }
-.vipPopup{
-  pointer-events: initial !important;
-  & *{
-    pointer-events: initial !important;
-  }
-}

+ 21 - 19
src/page-instrument/component/vip/index.tsx

@@ -7,31 +7,25 @@ import dayjs from "dayjs"
 import { storeData } from "/src/store"
 import { postMessage } from "/src/utils/native-message"
 import { usePageVisibility } from "@vant/use"
-import { studentQueryUserInfo } from "/src/page-instrument/api"
+import { getMusicSheetDetail } from "/src/utils/baseApi"
 import { api_back } from "/src/helpers/communication"
 
 export const vipData = reactive({
    show: false
 })
 
-export const isVip = computed(() => {
-   return dayjs().isBefore(dayjs(storeData.user?.membershipEndTime))
-})
-
 export default defineComponent({
    name: "vip-popup",
    setup() {
       const getContent = computed(() => {
-         if (state.isHomeWork) {
-            return "您还不是团练宝会员,请开通服务后使用该功能"
-         } else if (state.isSchool) {
+         if (state.isSchool) {
             return "VIP曲目暂不可用"
          } else {
-            return "您尚未开通云练习服务,请联系乐团老师开通"
+            return "该曲目暂未解锁,解锁更多曲目,享受全新学习体验"
          }
       })
       onMounted(() => {
-         if (state.isHomeWork && !isVip.value && state.paymentType === "VIP") {
+         if (state.isHomeWork && state.paymentType === "LOCK") {
             vipData.show = true
          }
       })
@@ -71,11 +65,12 @@ export default defineComponent({
       const pageVisibility = usePageVisibility()
       watch(pageVisibility, value => {
          if (state.isHomeWork && value === "visible") {
-            if (!isVip.value) {
-               studentQueryUserInfo().then(res => {
+            if (state.paymentType === "LOCK") {
+               getMusicSheetDetail(state.examSongId).then(res => {
                   if (res.code === 200) {
-                     storeData.user.membershipEndTime = res?.data?.membershipEndTime
-                     if (isVip.value) {
+                     state.paymentType = res?.data?.useStatus
+                     // 当没有锁定的时候 去掉弹窗
+                     if(state.paymentType !== "LOCK"){
                         vipData.show = false
                      }
                   }
@@ -97,11 +92,18 @@ export default defineComponent({
                round
             >
                <div class={styles.vip}>
-                  <img src={TipsIcon} />
-                  <p>{getContent.value}</p>
-                  <Button class={styles.btn} round color="#01C1B5" onClick={hanldeOpen}>
-                     {state.isHomeWork ? "开通" : " 确定"}
-                  </Button>
+                  <div class={styles.title}>
+                     <div class={styles.tit}>温馨提示</div>
+                  </div>
+                  <div class={styles.text}>{getContent.value}</div>
+                  <div class={styles.btnCon}>
+                     <Button class={styles.btn} round onClick={handleClose}>
+                        取消
+                     </Button>
+                     <Button class={styles.btn} round color="#01C1B5" onClick={hanldeOpen}>
+                        {state.isHomeWork ? "去开通" : " 确定"}
+                     </Button>
+                  </div>
                </div>
             </Popup>
          </>

BIN
src/page-instrument/component/vip/tips.png


+ 3 - 2
src/page-instrument/evaluat-model/index.tsx

@@ -262,7 +262,7 @@ export default defineComponent({
         // 音符是否不需要评测
         let noteNeedEvaluat = item.hasGraceNote || ListenMode || dontEvaluatingMode || !!item?.voiceEntry?.ornamentContainer || !!item.noteElement?.speedInfo?.startWord?.includes('rit.') || item.skipMode
         noteNeedEvaluat = noteNeedEvaluat == true ? true : false;
-        const data = {
+        const data: any = {
           timeStamp: (start * 1000) / rate,
           duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
           frequency: item.frequency,
@@ -278,6 +278,7 @@ export default defineComponent({
           // isOrnament: !!note?.voiceEntry?.ornamentContainer,
           isTenutoSound,
           isStaccato: item?.voiceEntry?.isStaccato ? true : false, // 是否是重音
+          frequencyList: item.frequencyList, // 如果是和弦音符,需要添加多个音符的频率,用于评测
         };
         datas.push(data);
       }
@@ -522,7 +523,7 @@ export default defineComponent({
       // 如果打开了延迟检测开关,需要先发送开始检测的消息
       const delayData = await api_getDeviceDelay();
       console.log('设备的延迟值',delayData.content?.value)
-      if (delayData && delayData.content?.value <= 0) {
+      if (delayData && delayData.content?.value <= 0 && state.paymentType !== "LOCK") {
         await api_startDelayCheck({});
       } else {
         evaluatingData.checkEnd = true;

ファイルの差分が大きいため隠しています
+ 0 - 0
src/page-instrument/header-top/image/glMode.json


ファイルの差分が大きいため隠しています
+ 0 - 0
src/page-instrument/header-top/image/lxMode.json


ファイルの差分が大きいため隠しています
+ 0 - 0
src/page-instrument/header-top/image/pcMode.json


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

@@ -420,7 +420,7 @@
         width: 100%;
         display: flex;
         justify-content: space-between;
-        padding: 0 100px;
+        padding: 0 80px;
         position: relative;
         top: 50%;
         transform: translateY(-50%);

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

@@ -10,7 +10,7 @@ import Speed from "./speed";
 import { evaluatingData, handleStartEvaluat } from "/src/view/evaluating";
 import Settting from "./settting";
 import state, { IPlatform, handleChangeSection, handleResetPlay, handleRessetState, togglePlay, IPlayState, refreshMusicSvg, EnumMusicRenderType, resetSettings, handleGuide, resetCursorPosition } from "/src/state";
-import { getAudioCurrentTime } from "/src/view/audio-list";
+import { audioData, getAudioCurrentTime } from "/src/view/audio-list";
 import { followData, toggleFollow } from "/src/view/follow-practice";
 import { api_back } from "/src/helpers/communication";
 import MusicType from "./music-type";
@@ -373,6 +373,8 @@ export default defineComponent({
       if (state.modeType === "follow") return { display: false, disabled: false };
       // 评测开始 禁用
       if (state.modeType === "evaluating") return { display: false, disabled: true };
+      // 总谱渲染在播放过程中 不能切换 
+      if(state.isCombineRender && state.playState === "play") return { display: true, disabled: true } 
       if (!state.isAppPlay) {
         if (state.playType === "play") {
           // 原声, 伴奏 少一个,就不能切换
@@ -821,6 +823,11 @@ export default defineComponent({
                   }
                 }
                 handlerModeChange(oldPlayType, oldPlaySource);
+                // 总谱 并且开启了单个声轨音频时候
+                if(state.isCombineRender && state.playSource === "background") {
+                  audioData.combineIndex = -1
+                  state.music = ""
+                }
                 showToast({
                   message: state.playType === "play" ? (state.playSource === "music" ? "已切换为原声" : "已切换为伴奏") : state.playSource === "music" ? "已切换为范唱" : state.playSource === "background" ? "已切换为伴唱" : "已切换为唱名",
                   position: "top",

+ 39 - 39
src/page-instrument/header-top/modeView.tsx

@@ -24,27 +24,27 @@ import { browser } from "/src/utils";
 export default defineComponent({
   name: "modeView",
   setup() {
-    // const modeImgDom1 = ref();
-    // const modeImgDom2 = ref();
-    // const modeImgDom3 = ref();
-    // watch(
-    //   () => headTopData.modeType,
-    //   (value, oldValue) => {
-    //     // headTopData.modeType 值 刚开始是 ""  所以 第一次切换时候不触发播放动画
-    //     if (!oldValue) return;
-    //     nextTick(() => {
-    //       if (value === "show") {
-    //         modeImgDom1.value?.pause();
-    //         modeImgDom2.value?.pause();
-    //         modeImgDom3.value?.pause();
-    //       } else if (value === "init") {
-    //         modeImgDom1.value?.play();
-    //         modeImgDom2.value?.play();
-    //         modeImgDom3.value?.play();
-    //       }
-    //     });
-    //   }
-    // );
+    const modeImgDom1 = ref();
+    const modeImgDom2 = ref();
+    const modeImgDom3 = ref();
+    watch(
+      () => headTopData.modeType,
+      (value, oldValue) => {
+        // headTopData.modeType 值 刚开始是 ""  所以 第一次切换时候不触发播放动画
+        if (!oldValue) return;
+        nextTick(() => {
+          if (value === "show") {
+            modeImgDom1.value?.pause();
+            modeImgDom2.value?.pause();
+            modeImgDom3.value?.pause();
+          } else if (value === "init") {
+            modeImgDom1.value?.play();
+            modeImgDom2.value?.play();
+            modeImgDom3.value?.play();
+          }
+        });
+      }
+    );
     watch(
       () => evaluatingData.socketErrorStatus,
       () => {
@@ -76,36 +76,36 @@ export default defineComponent({
           }}
         />
         <div class={[styles.modeBox, ((!state.isPercussion && !state.enableEvaluation) || (state.isPercussion && state.enableEvaluation) || (state.isPercussion && !state.enableEvaluation)) && styles.twoModeBox]}>
-          {/* <Vue3Lottie ref={modeImgDom1} class={styles.modeImg} animationData={lxMode} autoPlay={false} loop={true} onClick={() => {
+          <Vue3Lottie ref={modeImgDom1} class={styles.modeImg} animationData={lxMode} autoPlay={false} loop={true} onClick={() => {
             if(state.isSingleLine){
               smoothAnimationState.isShow.value = state.melodyLine;
             }
             headTopData.handleChangeModeType("practise")
-            }}></Vue3Lottie> */}
-          <img class={styles.modeImg} src={lxImg} 
+            }}></Vue3Lottie>
+          {/* <img class={styles.modeImg} src={lxImg} 
             onClick={() => {
               if(state.isSingleLine){
                 smoothAnimationState.isShow.value = state.melodyLine;
               }
               headTopData.handleChangeModeType("practise")
-            }} />
+            }} /> */}
           {!state.isPercussion && 
-              // <Vue3Lottie ref={modeImgDom2} class={styles.modeImg} animationData={glMode} autoPlay={false} loop={true} onClick={() => headTopData.handleChangeModeType("follow")}></Vue3Lottie>
-              <img class={styles.modeImg} src={glImg} 
-                onClick={() => headTopData.handleChangeModeType("follow")} />
+              <Vue3Lottie ref={modeImgDom2} class={styles.modeImg} animationData={glMode} autoPlay={false} loop={true} onClick={() => headTopData.handleChangeModeType("follow")}></Vue3Lottie>
+              /* <img class={styles.modeImg} src={glImg} 
+                onClick={() => headTopData.handleChangeModeType("follow")} /> */
           }
           {state.enableEvaluation && 
-            // <Vue3Lottie ref={modeImgDom3} class={styles.modeImg} animationData={pcMode} autoPlay={false} loop={true} onClick={() => {
-            //   // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
-            //   evaluatingData.needCheckErjiStatus = true;
-            //   headTopData.handleChangeModeType("evaluating")
-            // }}></Vue3Lottie>
-            <img class={styles.modeImg} src={pcImg} 
-              onClick={() => {
-                  // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
-                  evaluatingData.needCheckErjiStatus = true;
-                  headTopData.handleChangeModeType("evaluating")
-                }} />
+            <Vue3Lottie ref={modeImgDom3} class={styles.modeImg} animationData={pcMode} autoPlay={false} loop={true} onClick={() => {
+               // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
+              evaluatingData.needCheckErjiStatus = true;
+              headTopData.handleChangeModeType("evaluating")
+            }}></Vue3Lottie>
+            // <img class={styles.modeImg} src={pcImg} 
+            //   onClick={() => {
+            //       // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
+            //       evaluatingData.needCheckErjiStatus = true;
+            //       headTopData.handleChangeModeType("evaluating")
+            //     }} />
           }
         </div>
         {/** 延迟检测中途,socket出错,网络提示弹窗 */}

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

@@ -66,6 +66,7 @@
                     font-size: 15px;
                     color: #000000;
                     line-height: 21px;
+                    word-break: keep-all;
                 }
                 .titbtn {
                     width: 78px;
@@ -178,6 +179,11 @@
                         }
                     }
                 }
+                .qhBox {
+                    >div {
+                        width: 56px;
+                    }
+                }
                 // .speBox {
                 //     >div {
                 //         width: 48px;

+ 18 - 2
src/page-instrument/header-top/settting/index.tsx

@@ -84,7 +84,23 @@ export default defineComponent({
                                     <div class={styles.tit}>循环播放</div>
                                     <Switch v-model={state.setting.repeatAutoPlay}></Switch>
                                 </div>
-                        }                        
+                        }      
+                        {   !state.isCombineRender &&                    
+                            <div class={styles.cellBox}>
+                                <div class={styles.tit}>合并休止小节</div>
+                                <Switch 
+                                    v-model={state.setting.combineMultipleRest}
+                                    onChange={ async (value) => {
+                                        await checkMoveNoSave();
+                                        headTopData.settingMode = false
+                                        const _time = setTimeout(() => {
+                                            clearTimeout(_time)
+                                            refreshMusicSvg();
+                                        }, 100);
+                                    }}
+                                ></Switch>
+                            </div>
+                        }                                           
                         {/* {
                             state.isSingleLine && state.modeType === "practise" && !state.isCombineRender && !state.isPercussion && 
                                 <div class={styles.cellBox}>
@@ -230,7 +246,7 @@ export default defineComponent({
                             ["practise", "evaluating"].includes(state.modeType) ? 
                             <div class={styles.cellBox}>
                                 <div class={styles.tit}>切换谱面</div>
-                                <div class={styles.radioBox}>
+                                <div class={[styles.radioBox, styles.qhBox]}>
                                     {
                                         [{name:'单行谱',value:true},{name:'多行谱',value:false}].map(item=>{
                                             return <div class={ state.isSingleLine===item.value && styles.active } onClick={ async ()=>{ 

+ 8 - 5
src/page-instrument/header-top/speed/index.module.less

@@ -1,7 +1,10 @@
 .speedContainer{
     width: 334px;
     &.isHideBeat .content{
-        height: 172px;
+        height: 206px;
+        .speedSel{
+            padding-bottom: 0px !important;
+        }
     }
     .head{
         height: 42px;
@@ -25,7 +28,7 @@
     }
     .content{
         margin-top: -26px;
-        height: 230px;
+        height: 264px;
         background: #FFFFFF;
         border-radius: 16px;
         padding: 36px 16px 16px 16px;
@@ -122,8 +125,8 @@
                 }
             }
             .speedSel{
-                margin-top: 20px;
-                padding-bottom: 8px;
+                margin-top: 8px;
+                padding-bottom: 18px;
                 display: flex;
                 justify-content: space-between;
                 flex-wrap: wrap;
@@ -138,7 +141,7 @@
                     font-size: 13px;
                     color: rgba(0,0,0,0.6);
                     cursor: pointer;
-                    margin-bottom: 10px;
+                    margin-top: 10px;
                     margin-right: 3px;
                     &:active{
                         background: #B3EDE9;

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

@@ -39,7 +39,8 @@ export default defineComponent({
 				state.speed = speed.value
 				// handleSetSpeed(speed.value);
 				if (state.playState === 'paused') {
-					const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
+					// const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
+					const currentItem: any = state.times[state.activeNoteIndex];
 					state.basePlayRate = currentItem?.measureSpeed ? state.speed / currentItem.measureSpeed : state.speed / state.originSpeed;
 				}
 			}

+ 11 - 9
src/page-instrument/view-detail/index.tsx

@@ -315,15 +315,17 @@ export default defineComponent({
       // }
 
       // 管乐迷曲谱详情页,需要下载A4尺寸的图片
-      if (query.downPng === 'A4') {
-        const imgList = getSvgPngToSize(state.osmd)
-        console.log('A4', imgList)
-        window.parent.postMessage({
-          api: 'musicStaffRender',
-          loading: false,
-          osmdImg: imgList
-        }, '*');
-      }
+      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);
 
       state.musicScoreBtnDirection = state.playBtnDirection;
       state.musicRendered = true;

+ 53 - 11
src/state.ts

@@ -441,6 +441,8 @@ const state = reactive({
     reactionTimeMs: 0,
     /** 节拍器音量 */
     beatVolume: 50,
+    /** 合并休止小节 */
+    combineMultipleRest: true,
   },
   /** 后台设置的基准评测频率 */
   baseFrequency: 440,
@@ -602,6 +604,8 @@ const state = reactive({
   hasFollowResult: false,  
   /** 右上角速度图标,根据当前小节的速度是几分音符的动态变化 */
   speedIcon: 'speed3', // 默认取1/4拍的图片
+  /** xml的第一个measure标签的number */
+  firstMeasureNumber: 1,
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -684,6 +688,7 @@ export const onEnded = () => {
 
 // 根据当前小节动态设置,右上角展示的速度
 const dynamicShowPlaySpeed = (index: number, isPlaying?: boolean) => {
+
   //if (!headerColumnHide.value) {
     const item: any = state.times[index];
     if (item && item.measureSpeed ) {
@@ -827,7 +832,7 @@ export const skipNotePlay = async (itemIndex: number, isStart = false, handType?
     // 非选段模式,点击音符,动态设置右下角的速度
     if (item.measureSpeed && state.section.length < 2) {
       // console.log('速度3')
-      state.speed = state.basePlayRate * item.measureSpeed
+      state.speed = state.basePlayRate * 10000 * item.measureSpeed / 10000
     }
     setAudioCurrentTime(itemTime, itemIndex);
     // 一行谱,点击音符,或者播放完成,需要跳转音符位置
@@ -1184,7 +1189,8 @@ export const handleSetSpeed = (speed: number) => {
   // setStorageSpeed(state.examSongId, speed);
   state.speed = speed;
   // 当前的音符
-  const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.section[0] : state.times[state.activeNoteIndex];
+  // const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.section[0] : state.times[state.activeNoteIndex];
+  const currentItem: any = state.times[state.activeNoteIndex];
   state.basePlayRate = currentItem?.measureSpeed ? state.speed / currentItem.measureSpeed : state.speed / state.originSpeed;
   const actualRate = state.originAudioPlayRate * state.basePlayRate;
   console.log('速度设置',speed,'小节计算的倍率',state.basePlayRate,'实际播放倍率',actualRate)
@@ -1206,6 +1212,14 @@ export const handleChangeSection = () => {
     resetBaseRate(state.activeNoteIndex);
     //skipNotePlay(0, true);  取消选段的时候 不跳回开头
     state.sectionFirst = null;
+    // IOS18.1.1浏览器渲染更新有问题,需要手动更新一下
+    const selectionDom = document.getElementById('selectionBox')
+    if (selectionDom) {
+      selectionDom.style.display = 'none';
+      requestAnimationFrame(() => {
+        selectionDom.style.display = 'block';
+      })
+    }
     return;
   }
   state.sectionStatus = true;
@@ -1381,6 +1395,7 @@ export const scrollViewNote = (resetTop?: boolean) => {
   if (!cursorElement || !musicAndSelection) {
     return
   }
+  // console.log('滚动', musicAndSelection?.scrollTop, musicScrollTop)
   if (Math.abs(musicAndSelection?.scrollTop - musicScrollTop) > 30) {
     // 手动滑动谱面,重新播放需要滚动到对应位置
   } else {
@@ -1391,13 +1406,13 @@ export const scrollViewNote = (resetTop?: boolean) => {
     musicScrollTop = (offsetTop - state.headTopHeight - 30) * state.musicZoom
     musicAndSelection.scrollTo({
       top: (offsetTop - state.headTopHeight - 30) * state.musicZoom,
-      behavior: "smooth",
+      behavior: "auto",
     });
   } else {
     musicScrollTop = 0
     musicAndSelection.scrollTo({
       top: 0,
-      behavior: "smooth",
+      behavior: "auto",
     });
   }
 };
@@ -1454,10 +1469,21 @@ const getMusicInfo = async (res: any) => {
   state.isScoreRender = res.data?.isScoreRender
   // 是否默认显示总谱
   state.defaultScoreRender = res.data?.defaultScoreRender
+  /* 获取声轨列表 */
+  let xmlString = await fetch(res.data.xmlFileUrl).then((response) => response.text());
+  xmlString = xmlAddPartName(xmlString);
+  downloadXmlStr.value = xmlString //给musice-score 赋值xmlString 以免加载2次
+  const tracks = xmlToTracks(xmlString) //获取声轨列表  
   // 是否显示节拍器  (管乐迷 默认显示节拍器)
   //state.isMixBeat = res.data?.isMixBeat  
   /* 设置partIndex */
   let partIndexs = query["part-index"] ? query["part-index"].split(",") : ["-1"] // -1为partIndex没有值的时候
+  // 如果传入的是part-name,需要将part-name转换成part-index
+  if (query["part-name"]) {
+    const partValue = decodeURIComponent(query["part-name"]) || ''
+    let nameIdx = tracks.findIndex((item: any) => item == partValue)
+    partIndexs = [nameIdx]
+  }  
   partIndexs = partIndexs.map((indexStr:string) => {
     return parseInt(indexStr)
   }).sort((a, b) => a - b);
@@ -1476,11 +1502,7 @@ const getMusicInfo = async (res: any) => {
   // multiTracksSelection 返回为空,默认代表全部分轨
   state.canSelectTracks = res.data.multiTracksSelection === "null" || res.data.multiTracksSelection === "" || res.data.multiTracksSelection === null ? [] : res.data.multiTracksSelection?.split(',');
   state.canSelectTracks = state.canSelectTracks.map((item: any)=>item.trim())
-  /* 获取声轨列表 */
-  let xmlString = await fetch(res.data.xmlFileUrl).then((response) => response.text());
-  xmlString = xmlAddPartName(xmlString);
-  downloadXmlStr.value = xmlString //给musice-score 赋值xmlString 以免加载2次
-  const tracks = xmlToTracks(xmlString) //获取声轨列表
+
   // 如果是多个分轨合并显示的,需要记录下所选分轨的第一个分轨的名字,渲染计算音符位置的时候需要根据第一个分轨找到对应音符的位置
   if (state.combinePartIndexs.length) {
     (window as any).DYFirstTrackName = tracks[state.combinePartIndexs[0]] || '';
@@ -1512,7 +1534,10 @@ function xmlToTracks(xmlString: string) {
   const partNames = Array.from(xmlParse.getElementsByTagName('part-name'));
   return partNames.reduce((arr: string[], item) => {
     const textContent = item?.textContent?.trim()
-    if (textContent != "COMMON" && textContent != "common" && textContent) {
+    if (textContent?.toLocaleLowerCase() === "common") {
+      (window as any).HasCommonTrack = true;
+    }
+    if (textContent?.trim()?.toLocaleLowerCase() !== "common" && textContent) {
       arr.push(textContent)
     }
     return arr
@@ -1581,6 +1606,23 @@ function initMusicSource(data: any, tracks: string[], partIndex: number, workRec
             audioBeatMixUrl: banSongObj.scoreAudioBeatMixUrl
           }
         }
+        // 总谱 需要播放各个声部的音频
+        if(state.combinePartIndexs.length) {
+          // 当选择多个分轨时候
+          state.combinePartIndexs.map( partI => {
+            const musicSheetSound = musicSheetSoundList.find((item:any)=>{
+              return item.track?.toLowerCase().trim() === tracks[partI]?.toLowerCase().trim()
+            })
+            musicSheetSound?.audioFileUrl && (audioData.combineMusics[partI] = musicSheetSound.audioFileUrl)
+          })
+        }else{
+          tracks.map((itemTrack:any, partI:number) => {
+            const musicSheetSound = musicSheetSoundList.find((item:any)=>{
+              return item.track?.toLowerCase().trim() === itemTrack?.toLowerCase().trim()
+            })
+            musicSheetSound?.audioFileUrl && (audioData.combineMusics[partI] = musicSheetSound.audioFileUrl)
+          })
+        }
         // 总谱演奏模式是 伴奏
         accompanyObj = musicSheetAccompanimentList.find((item: any) => {
           return item.audioPlayType === "PLAY"
@@ -1726,7 +1768,7 @@ const setState = (data: any, index: number) => {
   state.appName = "COLEXIU";
   state.detailId = data.bizId;
   state.xmlUrl = data.xmlFileUrl;
-  state.paymentType = data.paymentType
+  state.paymentType = data.useStatus
   state.partIndex = index >= 0 ? index : 0;
   state.trackId = data.track;
   state.subjectId = data.subjectIds ? data.subjectIds.split(',')?.[0] : 0;

+ 0 - 2
src/store.ts

@@ -5,8 +5,6 @@ type IUser = {
   nickname?: string;
   /** 真实姓名 */
   realName?: string;
-  /** 会员结束时间 */
-  membershipEndTime?: string;
   tenantId?: number;
   /** 声部名称 */
   subjectId?: any;

+ 1 - 1
src/utils/crunker.ts

@@ -16,7 +16,7 @@ export default class Crunker {
       this._sampleRate = sampleRate
       this._concurrentNetworkRequests = concurrentNetworkRequests
    }
-   private _createContext(sampleRate = 44_100): AudioContext {
+   private _createContext(sampleRate = 22050): AudioContext {
       window.AudioContext = window.AudioContext || (window as any).webkitAudioContext || (window as any).mozAudioContext
       return new AudioContext({ sampleRate })
    }

+ 141 - 69
src/view/audio-list/index.tsx

@@ -18,6 +18,7 @@ import Crunker from "/src/utils/crunker"
 import tickMp3 from "/src/assets/tick.wav"
 import tockMp3 from "/src/assets/tock.wav"
 import { metronomeData } from "/src/helpers/metronome";
+import { showToast } from "vant"
 
 export const audioData = reactive({
 	songEle: null as HTMLAudioElement | null, // 原生
@@ -44,7 +45,10 @@ export const audioData = reactive({
 		mingSongGirlEle: null as HTMLAudioElement | null,
 		beatMingSongEle: null as HTMLAudioElement | null,
 		beatMingSongGirlEle: null as HTMLAudioElement | null
-	}
+	},
+	combineIndex: -1, // 当前 播放的总谱音频索引
+	combineMusics: {} as Record<string, any>, // 音频 url
+	combineMusicEles:[] as {key:number, value:HTMLAudioElement, beatValue:HTMLAudioElement|null}[] // 存储的音频el 当大于4个时候删除
 });
 const midiRef = ref();
 /** 播放或暂停 */
@@ -204,6 +208,141 @@ export const changeMingSongType = () =>{
 		audioData.songCollection.beatMingSongEle = mingSongType === 1 ? beatMingSongEle : beatMingSongGirlEle
 	}
 }
+
+const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
+	if(!src){
+		return Promise.resolve(null)
+	}
+	return new Promise((resolve) => {
+		const a = new Audio(src + '?v=' + Date.now());
+		a.onloadedmetadata = () => {
+			resolve(a);
+		};
+		a.onerror = () => {
+			resolve(null);
+		};
+		// 当未加载 资源之前 切换到其他浏览器标签,浏览器可能会禁止资源加载所以无法触发onloadedmetadata事件,导致一直在加载中,这里做个兼容
+		if (document.visibilityState === 'visible') {
+			a.load();
+		} else {
+			const onVisibilityChange = () => {
+				if (document.visibilityState === 'visible') {
+					document.removeEventListener('visibilitychange', onVisibilityChange);
+					a.load();
+				}
+			};
+			document.addEventListener('visibilitychange', onVisibilityChange);
+		}
+	});
+};
+
+// 合成节拍器资源
+const crunker = new Crunker()
+async function mergeBeatAudio(music?:string, accompany?:string){
+	let beatMusic, beatAccompany
+	if(!state.isMixBeat) {
+		return [beatMusic, beatAccompany]
+	}
+	console.time("音频合成时间")
+	try{
+		console.time("音频加载时间")
+		const [musicBuff, accompanyBuff, tickBuff, tockBuff] = await crunker.fetchAudio(music?`${music}?v=${Date.now()}`:undefined, accompany?`${accompany}?v=${Date.now()}`: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 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])
+		console.timeEnd("音频合并时间")
+		console.time("音频audioDom生成时间")
+		beatMusic = musicBuffMeg && crunker.exportAudioElement(musicBuffMeg)
+		beatAccompany = accompanyBuffMeg && crunker.exportAudioElement(accompanyBuffMeg)
+		console.timeEnd("音频audioDom生成时间")
+	}catch(err){
+		console.log(err)
+	}
+	console.timeEnd("音频合成时间")
+	return [beatMusic, beatAccompany]
+}
+// 切换对应的声轨,并且配置当前的audio
+export async function changeCombineAudio (combineIndex: number){
+	// 重复点击的时候取消选中 原音
+	if(combineIndex === audioData.combineIndex){
+		audioData.combineIndex = -1
+		state.playSource = "background"
+		state.music = ""
+		// 当没有背景音文件的时候
+		if(!state.accompany) {
+			state.noMusicSource = true
+		}
+		return
+	}
+	state.loadingText = "音频资源加载中,请稍后…";
+	state.isLoading = true;
+	const musicUrl = audioData.combineMusics[combineIndex]
+	// 有就拿缓存,没有就加载
+	const cacheMusicIndex = audioData.combineMusicEles.findIndex(ele => {
+		return ele.key === combineIndex
+	})
+	const cacheMusic = audioData.combineMusicEles[cacheMusicIndex]
+	if(cacheMusic?.value){
+		audioData.songCollection.songEle = cacheMusic.value
+		audioData.songCollection.beatSongEle = cacheMusic.beatValue
+		// 使用缓存之后 当前数据位置向后偏移,删除缓存的时候以使用顺序位置
+		const itemMusic = audioData.combineMusicEles.splice(cacheMusicIndex, 1)
+		audioData.combineMusicEles.push(...itemMusic)
+	}else{
+		const music = await createAudio(musicUrl)
+		const [beatMusic] = await mergeBeatAudio(musicUrl)
+		// 当没有背景音的时候 需要绑定事件
+		if(!state.accompany){
+			if(music){
+				music.addEventListener("play", onPlay);
+				music.addEventListener("ended", onEnded);
+			}			
+			if(beatMusic){
+				beatMusic.addEventListener("play", onPlay);
+				beatMusic.addEventListener("ended", onEnded);
+			}
+		}
+		audioData.combineMusicEles.push({
+			key: combineIndex,
+			value: music!,
+			beatValue: beatMusic!
+		})
+		// 当大于4个数据的时候 删除掉最前面的数据
+		if(audioData.combineMusicEles.length > 4){
+			audioData.combineMusicEles.splice(0,1)
+		}
+		audioData.songCollection.songEle = music
+		audioData.songCollection.beatSongEle = beatMusic!
+	}
+	audioData.combineIndex = combineIndex
+	state.music = musicUrl
+	state.playSource = "music"
+	// 当没有背景音文件的时候
+	if(!state.accompany) {
+		state.noMusicSource = false
+	}
+	showToast({
+		message:  "已开启原声",
+		position: "top",
+		className: "selectionToast",
+	});
+	state.isLoading = false;
+}
 export default defineComponent({
 	name: "audio-list",
 	setup() {
@@ -238,33 +377,6 @@ export default defineComponent({
 			}
 		);
 
-		const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
-			if(!src){
-				return Promise.resolve(null)
-			}
-			return new Promise((resolve) => {
-				const a = new Audio(src + '?v=' + Date.now());
-				a.onloadedmetadata = () => {
-					resolve(a);
-				};
-				a.onerror = () => {
-					resolve(null);
-				};
-				// 当未加载 资源之前 切换到其他浏览器标签,浏览器可能会禁止资源加载所以无法触发onloadedmetadata事件,导致一直在加载中,这里做个兼容
-				if (document.visibilityState === 'visible') {
-					a.load();
-				} else {
-					const onVisibilityChange = () => {
-						if (document.visibilityState === 'visible') {
-							document.removeEventListener('visibilitychange', onVisibilityChange);
-							a.load();
-						}
-					};
-					document.addEventListener('visibilitychange', onVisibilityChange);
-				}
-			});
-		};
-
 		/**
 		 * #11046
 		 * 声音与圆点消失的节点不一致,可能原因是部分安卓手机没有立即播放,所以需要等待有音频进度返回时再播放节拍器
@@ -338,46 +450,6 @@ export default defineComponent({
 		function loadBeatAudio(){
 			return Promise.all([createAudio(state.beatSong.music), createAudio(state.beatSong.accompany), createAudio(state.beatSong.fanSong), createAudio(state.beatSong.banSong), createAudio(state.beatSong.mingSong), createAudio(state.beatSong.mingSongGirl)])
 		}
-		// 合成节拍器资源
-		async function mergeBeatAudio(){
-			let beatMusic, beatAccompany
-			if(!state.isMixBeat) {
-				return [beatMusic, beatAccompany]
-			}
-			console.time("音频合成时间")
-			try{
-				const crunker = new Crunker()
-				console.time("音频加载时间")
-				const [musicBuff, accompanyBuff, tickBuff, tockBuff] = await crunker.fetchAudio(state.music?`${state.music}?v=${Date.now()}`:undefined, state.accompany?`${state.accompany}?v=${Date.now()}`: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 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])
-				console.timeEnd("音频合并时间")
-				console.time("音频audioDom生成时间")
-				beatMusic = musicBuffMeg && crunker.exportAudioElement(musicBuffMeg)
-				beatAccompany = accompanyBuffMeg && crunker.exportAudioElement(accompanyBuffMeg)
-				console.timeEnd("音频audioDom生成时间")
-			}catch(err){
-				console.log(err)
-			}
-			console.timeEnd("音频合成时间")
-			return [beatMusic, beatAccompany]
-		}
 		onMounted(async () => {
 			// 预览的时候不走音频加载逻辑
 			if(state.isPreView){
@@ -430,7 +502,7 @@ export default defineComponent({
 				// 处理带节拍器的音源
 				//const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await loadBeatAudio()
 				// 客户端合成节拍器
-				const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await mergeBeatAudio()
+				const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await mergeBeatAudio(state.music, state.accompany)
 				Object.assign(audioData.songCollection, {
 					beatSongEle:beatMusic,
 					beatBackgroundEle:beatAccompany,

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

@@ -352,11 +352,11 @@ export const matchVoicePart = (id: number | string, type: "SINGLE" | "CONCERT"):
         let pitchKey = sKey;
         if (typeof sKey === "string" && isNaN(Number(sKey)) ) {
           pitchKey = pitchKey.toLocaleLowerCase().replace(/ /g, "");
-          pitchKey = pitchKey.replace(/[_0-9]+$/, '');
+          pitchKey = pitchKey.replace(/[_0-9.]+$/, '');
         }
         if (typeof sKey === "string") {
           // 去掉声轨后面的数字
-          code = code.replace(/[_0-9]+$/, '');
+          code = code.replace(/[_0-9.]+$/, '');
         }
         if (pitchKey === code) {
           _track = subject[sKey];

BIN
src/view/music-score/combineAudio/imgs/lock.png


BIN
src/view/music-score/combineAudio/imgs/open.png


+ 18 - 0
src/view/music-score/combineAudio/index.module.less

@@ -0,0 +1,18 @@
+.combineAudio {
+   position: absolute;
+   left: 0;
+   top: 0;
+   z-index: 1;
+   .combineAudioImg {
+      position: absolute;
+      z-index: 119;
+      width: 22PX;
+      height: 22PX;
+      padding: 2PX;
+      transform: scale(var(--combineZoom));
+   }
+   &.play .combineAudioImg {
+      pointer-events: none;
+      opacity: 0.4;
+   }
+}

+ 68 - 0
src/view/music-score/combineAudio/index.tsx

@@ -0,0 +1,68 @@
+import { defineComponent, onMounted, ref, computed } from "vue"
+import styles from "./index.module.less"
+import { audioData, changeCombineAudio } from "/src/view/audio-list"
+import openImg from "./imgs/open.png"
+import lockImg from "./imgs/lock.png"
+import state from "/src/state"
+
+export default defineComponent({
+   name: "combineAudio",
+   setup(props, { emit }) {
+      const elementsData = ref<{ index: number; top: number; left: number }[]>([])
+      onMounted(() => {
+         const parent = document.querySelector("#osmdCanvasPage1")
+         const elements = document.querySelectorAll("g[data-trackIdx]")
+         const musicContainerPos = document.getElementById("musicAndSelection")?.getBoundingClientRect() || {
+            top: 0,
+            left: 0
+         }
+         const combineMusicsIndexs = Object.keys(audioData.combineMusics)
+         elements.forEach(element => {
+            const dataTrackIdx = element.getAttribute("data-trackIdx")
+            // 当有 dataTrackIdx 并且有原音的时候 显示
+            if (dataTrackIdx && combineMusicsIndexs.includes(dataTrackIdx)) {
+               const elementRect = element.getBoundingClientRect()
+               const height = elementRect.height
+               let top = elementRect.top + height / 2 - 11 - musicContainerPos.top
+               let left = elementRect.left - 22 - 10 - musicContainerPos.left
+               elementsData.value.push({
+                  index: parseInt(dataTrackIdx),
+                  top: top,
+                  left: left
+               })
+            }
+         })
+      })
+      const combineZoom = computed(() => {
+         let zoom = state.zoom
+         if (zoom < 1) {
+            zoom = 1
+         } else if (zoom > 1.5) {
+            zoom = 1.5
+         }
+         return zoom
+      })
+      return () => (
+         <>
+            <div class={[styles.combineAudio, state.playState === "play" && styles.play]}>
+               {elementsData.value.map(item => {
+                  return (
+                     <img
+                        class={styles.combineAudioImg}
+                        onClick={() => {
+                           changeCombineAudio(item.index)
+                        }}
+                        style={{
+                           top: item.top + "px",
+                           left: item.left - (combineZoom.value - 1) * 22 + "px",
+                           "--combineZoom": combineZoom.value
+                        }}
+                        src={audioData.combineIndex === item.index ? openImg : lockImg}
+                     />
+                  )
+               })}
+            </div>
+         </>
+      )
+   }
+})

+ 8 - 5
src/view/music-score/index.tsx

@@ -11,6 +11,7 @@ import { resetFormate, resetGivenFormate, setGlobalMusicSheet, limitSingleSvgPag
 import { setGlobalData } from "/src/utils";
 import { storeData } from "/src/store";
 import HorizontalDragScroll from './HorizontalDragScroll';
+import CombineAudio from './combineAudio';
 import { getQuery } from "/src/utils/queryString";
 
 export const musicRenderTypeKey = "musicRenderType";
@@ -97,7 +98,6 @@ export default defineComponent({
 					drawComposer: false, // 渲染作词家
 					// pageBackgroundColor: '#609FCF',
 					// autoGenerateMultipleRestMeasuresFromRestMeasures: state.isSingleLine ? false : true, // 连续休止小节是否合并显示
-					autoGenerateMultipleRestMeasuresFromRestMeasures: true,
 					// darkMode: true, // 暗黑模式
 					// pageFormat: 'A4_P',
 					// autoBeam: true,
@@ -115,7 +115,8 @@ export default defineComponent({
 				drawLyrics: (((!state.accompany && !state.music ) || state.playType === 'sing' || !state.isEvxml) && !state.isSimplePage) ? true : false, // 演唱模式才渲染歌词,simple页面不显示歌词
 				drawPartNames: props.showPartNames, // 是否渲染声轨名称
 				defaultColorMusic: props.musicColor, // 颜色
-				renderSingleHorizontalStaffline: state.isSingleLine ? true : false
+				renderSingleHorizontalStaffline: state.isSingleLine ? true : false,
+				autoGenerateMultipleRestMeasuresFromRestMeasures: state.setting.combineMultipleRest, // 是否自动合并休止小节
 			})
 			// osmd.EngravingRules.CompactMode = true // 紧凑模式
 			// osmd.EngravingRules.PageRightMargin = state.isSingleLine ? (window.innerWidth+200)/10 : 2;
@@ -139,7 +140,7 @@ export default defineComponent({
 				// osmd.EngravingRules.PageTopMargin = state.isPreView ? 1 : 3;
 				osmd.EngravingRules.PageTopMargin = (state.isPreView && state.musicRenderType === EnumMusicRenderType.staff) ? 1 : state.isPreView ? 2 : 3;
 				osmd.EngravingRules.PageTopMarginNarrow = 3;
-				osmd.EngravingRules.PageLeftMargin = 3.6;
+				osmd.EngravingRules.PageLeftMargin = state.isCombineRender ? 8 : 3.6;
 				osmd.EngravingRules.PageRightMargin = 3;
 				osmd.EngravingRules.BreathMarkDistance = 0.1; // 呼吸标记距离音符的位置,百分比
 				osmd.EngravingRules.PageBottomMargin = state.isSingleLine ? 2 : 18;
@@ -169,12 +170,13 @@ export default defineComponent({
 				const canSelectTracks = state.combinePartIndexs.length > 1 ? state.combinePartIndexs.map(partIndex => { return state.partListNames[partIndex] }) : state.canSelectTracks
 				for (let i = 0; i < osmd.Sheet.Instruments.length; i++) {
 					const trackName = state.isEvxml && state.evxmlAddPartName ? osmd.Sheet.Instruments[i].idString || '' : osmd.Sheet.Instruments[i].Name || '';
-					osmd.Sheet.Instruments[i].Visible = canSelectTracks.includes(trackName)
+					osmd.Sheet.Instruments[i].Visible = canSelectTracks.includes(trackName.trim())
 				}
 			}
 			if (query.downPng === 'A4') {
+				osmd.EngravingRules.PageTopMargin = 5
 				osmd.setPageFormat('794x1100')
-				osmd.zoom = 0.3;
+				osmd.zoom = query.zoom || 0.3;
 			} else {
 				osmd.zoom = state.zoom;
 			}
@@ -260,6 +262,7 @@ export default defineComponent({
 			>
 				{slots.default?.()}
 				{props.showSelection && musicData.showSelection && !state.isEvaluatReport &&!state.isSimplePage && !state.isPreView && state.musicRendered && <Selection />}
+				{props.showSelection && musicData.showSelection && state.isCombineRender &&!state.isSimplePage && !state.isPreView && state.musicRendered && <CombineAudio></CombineAudio> }
 			</div>
 		);
 	},

+ 23 - 3
src/view/plugins/move-music-score/index.tsx

@@ -219,6 +219,12 @@ export const filterMoveData = async () => {
 				};
 				if (n.type === "vf-lineGroup") {
 					item.dx = n.dx;
+					// 需要获取当前渐强渐弱线所对应的小节的宽度,算出相对当前宽度的比例,用于不同设备回显用
+					const measureNum = document.getElementById(n.id)?.getAttribute('data-mnum');
+					const measureWidth = measureNum ? document.querySelector(`g[data-num='${measureNum}']`)?.getBoundingClientRect()?.width : 0;
+					if (measureWidth) {
+						item.dxRate = n.dx / measureWidth;
+					}
 				}
 				if (n.id.includes('text')) {
 					// let copyDom = document.querySelector("#" + n.id)!.cloneNode(true) as SVGSVGElement
@@ -405,6 +411,7 @@ const resetMoveNote = () => {
 		moveData.modelList[i].isMove = false;
 		moveData.modelList[i].isDelete = false;
 		moveData.modelList[i].dx = 0;
+		moveData.modelList[i].dxRate = 0;
 		renderSvgItem(moveData.modelList[i]);
 		if (moveData.modelList[i].type === "vf-lineGroup") {
 			renderLineGroup(moveData.modelList[i]);
@@ -476,10 +483,23 @@ function renderLineGroup(lineGroup: any) {
 				dx2 = dx2[0] && !isNaN(Number(dx2[0])) ? Number(dx2[0]) : 0;
 
 				if (dx1 && dx2) {
+					// 根据dxRate比例转换dx
+					let targetDx = lineGroup.dx;
+					if (lineGroup.dxRate) {
+						// 需要获取当前渐强渐弱线所对应的小节的宽度,算出相对当前宽度的比例,用于不同设备回显用
+						const measureNum = document.getElementById(lineGroup.id)?.getAttribute('data-mnum');
+						const measureWidth = measureNum ? document.querySelector(`g[data-num='${measureNum}']`)?.getBoundingClientRect()?.width : 0;
+						targetDx = measureWidth ? measureWidth * lineGroup.dxRate : lineGroup.dx;
+					}
+					// targetDx = targetDx * state.zoom;
+					// console.log('targetDx',targetDx)
+					if (storeData.isApp) {
+						targetDx = targetDx * state.zoom;
+					}
 					if (dx1 < dx2) {
-						d = d.replace(dx2, lineGroup.d2 + lineGroup.dx + "");
+						d = d.replace(dx2, lineGroup.d2 + targetDx + "");
 					} else {
-						d = d.replace(dx1, lineGroup.d2 + lineGroup.dx + "");
+						d = d.replace(dx1, lineGroup.d2 + targetDx + "");
 					}
 					path.setAttribute("d", d);
 				}
@@ -688,4 +708,4 @@ export default defineComponent({
 			</div>
 		);
 	},
-});
+});

BIN
src/view/plugins/toggleMusicSheet/choosePartName/imgs/resetBtn.png


+ 11 - 7
src/view/plugins/toggleMusicSheet/choosePartName/index.module.less

@@ -1,5 +1,6 @@
 .container {
-  width: 334px;
+  width: 510px;
+  height: 320px;
   .head{
       height: 42px;
       position: relative;
@@ -22,7 +23,7 @@
   }
   .pickerCon{
     margin-top: -26px;
-    height: 290px;
+    height: 306px;
     background: #FFFFFF;
     border-radius: 16px;
     padding: 36px 20px 12px 20px;
@@ -40,7 +41,7 @@
       .titCon{
         display: flex;
         align-items: center;
-        padding: 10px 0;
+        padding-top: 10px;
         &.stickyTit{
           position: sticky;
           top: -1px;
@@ -61,8 +62,12 @@
         }
       }
       .content{
+        display: flex;
+        flex-wrap: wrap;
+        .specialBtn {
+          width: 96px;
+        }
         .selBtn{
-          width: 100%;
           height: 34px;
           line-height: 34px;
           background: #F6F6F6;
@@ -74,9 +79,8 @@
           cursor: pointer;
           border:1px solid transparent;
           margin-top: 10px;
-          &:first-child{
-            margin-top: 0;
-          }
+          margin-right: 10px;
+          padding: 0 8px;
           &.active{
             background: #F2FFFC;
             border-color: #01C1B5;

+ 19 - 6
src/view/plugins/toggleMusicSheet/choosePartName/index.tsx

@@ -7,6 +7,8 @@ import { headImg } from "/src/page-instrument/header-top/image";
 import { toggleMusicSheet } from "../index"
 import okBtn from "./imgs/okBtn.png"
 import cancelBtn from "./imgs/cancelBtn.png"
+import resetBtn from "./imgs/resetBtn.png"
+import { getQuery } from "/src/utils/queryString";
 
 export default defineComponent({
   name: 'choosePartName',
@@ -22,6 +24,7 @@ export default defineComponent({
   },
   emits: ['close'],
   setup(props, { emit }) {
+    const query: any = getQuery();
     const selValues = ref([...props.partIndexs]);
     watch(
       () => toggleMusicSheet.show,
@@ -76,11 +79,11 @@ export default defineComponent({
             {
               state.isScoreRender &&
                 <>
-                  <div class={styles.titCon}>
+                  {/* <div class={styles.titCon}>
                     <div class={styles.tit}>选择总谱</div>
-                  </div>
+                  </div> */}
                   <div class={styles.content}>
-                    <div class={[styles.selBtn, selValues.value.includes(999) && styles.active]} onClick={()=>{ hanldeSelSheet(999, true) }}>总谱</div>
+                    <div class={[styles.selBtn, styles.specialBtn, selValues.value.includes(999) && styles.active]} onClick={()=>{ hanldeSelSheet(999, true) }}>总谱</div>
                   </div>
                 </>
             }
@@ -100,12 +103,22 @@ export default defineComponent({
             </div>
           </div>
           <div class={styles.btnCon}>
-              <img src={ cancelBtn } class={styles.btn} onClick={async () => {
-                  emit('close')
+              <img src={ resetBtn } class={styles.btn} onClick={async () => {
+                  selValues.value = []
                 }
               }></img>
               <img src={ okBtn } class={styles.btn} onClick={async () => {
-                  await checkMoveNoSave();
+                  if (!selValues.value.length) {
+                    showToast({
+                      position: "top",
+                      message: "最少需要选择一个声部"
+                    });
+                    return
+                  }
+                  if (query.isMove) {
+                    await checkMoveNoSave();
+                  }
+                  toggleMusicSheet.show = false
                   nextTick(()=>{
                     emit('close', selValues.value)
                   })

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

@@ -77,6 +77,7 @@ export default defineComponent({
           behaviorId: sessionStorage.getItem('behaviorId') || '',
           _t: new Date().valueOf(),
           'part-index': index,
+          'part-name': ''
         })
       console.log(_url)
       location.href = _url

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

@@ -392,7 +392,7 @@
     :global {
         .node-dot::before{
             height: 120PX;
-            width: 3PX;
+            width: 4PX;
         }
     }
 }
@@ -405,7 +405,7 @@
     :global {
         .node-dot::before{
             height: 140PX;
-            width: 3PX;
+            width: 4PX;
         }
     }
 }
@@ -419,7 +419,7 @@
     :global {
         .node-dot::before{
             height: 45PX;
-            width: 1PX;
+            width: 2PX;
         }
     }
 }
@@ -433,7 +433,7 @@
     :global {
         .node-dot::before{
             height: 35PX;
-            width: 1PX;
+            width: 2PX;
         }
     }
 }

+ 8 - 0
src/view/selection/index.tsx

@@ -417,6 +417,14 @@ export default defineComponent({
 												className: "selectionToast",
 											});
 										}
+										// IOS18.1.1浏览器渲染更新有问题,需要手动更新一下
+										const selectionDom = document.getElementById('selectionBox')
+										if (selectionDom) {
+											selectionDom.style.display = 'none';
+											requestAnimationFrame(() => {
+												selectionDom.style.display = 'block';
+											})
+										}							
 									}}></div>
 								</div>
 							)

ファイルの差分が大きいため隠しています
+ 0 - 0
stats.html


+ 1 - 1
vite.config.ts

@@ -52,7 +52,7 @@ export default defineConfig({
     // https: true,
     proxy: {
       "^/instrument/.*": {
-        target: "https://dev.gym.lexiaoya.cn",
+        target: "https://test.gym.lexiaoya.cn",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/instrument/, ""),
       },

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません