index.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import { computed, defineComponent, onMounted, reactive, Transition } from "vue";
  2. import state, { EnumMusicRenderType, handleSelection, skipNotePlay, IPlatform } from "/src/state";
  3. import styles from "./index.module.less";
  4. import { metronomeData } from "/src/helpers/metronome";
  5. import { evaluatingData } from "../evaluating";
  6. import { leveByScoreMeasureIcons } from "../evaluating/evaluatResult";
  7. import { Icon } from "vant";
  8. import MoveMusicScore from "../plugins/move-music-score";
  9. import { useRoute } from "vue-router";
  10. import { getQuery } from "/src/utils/queryString";
  11. const selectData = reactive({
  12. notes: [] as any[],
  13. staves: [] as any[],
  14. });
  15. /** 计算点击层数据 */
  16. const calcNoteData = () => {
  17. const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || {
  18. x: 0,
  19. y: 0,
  20. };
  21. const parentLeft = musicContainer.x || 0;
  22. const parentTop = musicContainer.y || 0;
  23. const notes = state.times;
  24. const notesList: string[] = [];
  25. const MeasureNumberXMLList: number[] = [];
  26. for (let i = 0; i < notes.length; i++) {
  27. const item = notes[i];
  28. // console.log("🚀 ~ item:", item)
  29. const noteItem = {
  30. ...item,
  31. index: item.i,
  32. bbox: null as any,
  33. staveBox: null as any,
  34. };
  35. if (!notesList.includes(item.noteId)) {
  36. let staveBbox: any = {};
  37. if (item.stave?.attrs?.id) {
  38. const staveEle = document.querySelector(`#${item.stave.attrs.id}`);
  39. staveBbox = staveEle?.parentElement?.parentElement?.getBoundingClientRect?.() || {
  40. x: 0,
  41. width: 0,
  42. };
  43. // console.log("🚀 ~ staveBbox:", staveBbox)
  44. }
  45. if (item.svgElement) {
  46. const noteEle = document.querySelector(`#vf-${item.svgElement?.attrs?.id}`);
  47. if (noteEle) {
  48. const noteBbox = noteEle.getBoundingClientRect?.() || { x: 0, width: 0 };
  49. if (state.musicRenderType !== EnumMusicRenderType.staff) {
  50. noteItem.bbox = {
  51. left: noteBbox.x - parentLeft - noteBbox.width / 4 + "px",
  52. top: noteBbox.y - parentTop - noteBbox.height + "px",
  53. width: noteBbox.width * 1.5 + "px",
  54. height: noteBbox.height * 3 + "px",
  55. };
  56. const noteHead = noteEle.querySelector(".vf-numbered-note-head");
  57. const noteHeadBbox = noteHead?.getBoundingClientRect?.();
  58. if (noteHeadBbox) {
  59. item.bbox = {
  60. left: noteHeadBbox.x - parentLeft - noteHeadBbox.width / 4,
  61. width: noteHeadBbox.width * 1.5,
  62. }
  63. }
  64. } else {
  65. noteItem.bbox = {
  66. left: noteBbox.x - parentLeft - noteBbox.width / 4 + "px",
  67. top: staveBbox.y - parentTop + "px",
  68. width: noteBbox.width * 1.5 + "px",
  69. height: staveBbox.height + "px",
  70. };
  71. }
  72. }
  73. selectData.notes.push(noteItem);
  74. notesList.push(item.noteId);
  75. }
  76. }
  77. if (!MeasureNumberXMLList.includes(item.MeasureNumberXML)) {
  78. if (item.stave) {
  79. if (item.stave?.attrs?.id) {
  80. const staveEle = document.querySelector(`#${item.stave.attrs.id}`);
  81. const list = [
  82. Array.from(staveEle?.querySelectorAll(".vf-clef") || []),
  83. Array.from(staveEle?.querySelectorAll(".vf-keysignature") || []),
  84. Array.from(staveEle?.getElementsByTagName("text") || []),
  85. ].flat();
  86. try {
  87. if (list.length) {
  88. // console.log("🚀 ~ list:", list)
  89. list.forEach((_el: any) => {
  90. _el?.style?.setProperty("display", "none");
  91. });
  92. }
  93. } catch (error) {}
  94. const staveBbox = staveEle?.getBoundingClientRect?.() || { x: 0, width: 0, y: 0, height: 0 };
  95. try {
  96. if (list.length) {
  97. list.forEach((_el: any) => {
  98. _el?.style?.removeProperty("display");
  99. });
  100. }
  101. } catch (error) {}
  102. // console.log("🚀 ~ staveEle:", staveEle)
  103. noteItem.staveBox = {
  104. left: staveBbox.x - parentLeft + "px",
  105. // top: ((item.stave.y || 0) - 5) * state.zoom + "px",
  106. top: staveBbox.y - parentTop + "px",
  107. width: staveBbox.width + "px",
  108. height: staveBbox.height + "px",
  109. // background: 'rgba(0,0,0,.2)'
  110. };
  111. selectData.staves.push(noteItem);
  112. }
  113. MeasureNumberXMLList.push(item.MeasureNumberXML);
  114. } else {
  115. if (item.multipleRestMeasures) {
  116. const preItem = selectData.staves.find(
  117. (n: any) => n.MeasureNumberXML === item.MeasureNumberXML - 1
  118. );
  119. if (preItem?.staveBox) {
  120. noteItem.staveBox = {
  121. left: preItem.staveBox.left,
  122. top: preItem.staveBox.top,
  123. width: preItem.staveBox.width,
  124. };
  125. selectData.staves.push(noteItem);
  126. MeasureNumberXMLList.push(item.MeasureNumberXML);
  127. }
  128. }
  129. }
  130. }
  131. }
  132. // console.log("🚀 ~ selectData.notes:", selectData.staves);
  133. };
  134. /** 重新计算 */
  135. export const recalculateNoteData = () => {
  136. selectData.notes = [];
  137. selectData.staves = [];
  138. calcNoteData();
  139. };
  140. export default defineComponent({
  141. name: "selection",
  142. setup() {
  143. const route = useRoute();
  144. const query: any = {
  145. ...getQuery(),
  146. ...route.query,
  147. };
  148. /** 是否可以点击音符 */
  149. const disableClickNote = computed(() => {
  150. return state.sectionStatus || state.modeType !== "practise";
  151. });
  152. const showClass = computed(() => {
  153. return (item: any) => {
  154. if (state.sectionStatus) {
  155. if (state.section.length === 1) {
  156. if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
  157. return styles.leftStaveBox;
  158. }
  159. }
  160. if (state.section.length === 2) {
  161. // 选段预备拍背景
  162. if (state.sectionFirst && item.MeasureNumberXML === state.sectionFirst.MeasureNumberXML) {
  163. return styles.prepareStaveBox;
  164. }
  165. if (
  166. item.MeasureNumberXML >= state.section[0].MeasureNumberXML &&
  167. item.MeasureNumberXML <= state.section[1].MeasureNumberXML
  168. ) {
  169. if (
  170. item.MeasureNumberXML == state.section[0].MeasureNumberXML &&
  171. item.MeasureNumberXML == state.section[1].MeasureNumberXML
  172. ) {
  173. return styles.centerStaveBox;
  174. }
  175. if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
  176. return styles.leftStaveBox;
  177. }
  178. if (item.MeasureNumberXML == state.section[1].MeasureNumberXML) {
  179. return styles.rightStaveBox;
  180. }
  181. return styles.staveBox;
  182. }
  183. }
  184. } else {
  185. if (state.activeMeasureIndex == item.MeasureNumberXML && !state.isReport) {
  186. return styles.staveBox;
  187. }
  188. }
  189. };
  190. });
  191. onMounted(() => {
  192. calcNoteData();
  193. });
  194. return () => (
  195. <div
  196. id="selectionBox"
  197. class={styles.selectionContainer}
  198. onClick={(e: Event) => e.stopPropagation()}
  199. >
  200. {selectData.staves.map((item: any) => {
  201. const scoreItem = item.id && evaluatingData.evaluatings[item.measureListIndex];
  202. // 高级模式下,显示节拍线
  203. // 不是报告模式
  204. // 不是多小节休止符
  205. // 节拍线开关
  206. // 当前小节
  207. // 当前小节
  208. const lineShow =
  209. !state.isReport &&
  210. !state.times[state.activeNoteIndex].multipleRestMeasures &&
  211. metronomeData.cursorMode === 2 &&
  212. item.MeasureNumberXML === metronomeData.activeMetro?.measureNumberXML &&
  213. state.times[state.activeNoteIndex].MeasureNumberXML === item.MeasureNumberXML;
  214. return (
  215. <>
  216. {item.staveBox && (
  217. <div
  218. class={[
  219. styles.position,
  220. showClass.value(item),
  221. scoreItem ? `scoreItemLeve${scoreItem.leve}` : "",
  222. state.platform === IPlatform.PC ? styles.linePC : ''
  223. ]}
  224. style={item.staveBox}
  225. onClick={() => handleSelection(item)}
  226. >
  227. {lineShow && (
  228. <div
  229. class={[
  230. styles.line,
  231. state.musicRenderType == EnumMusicRenderType.staff ? styles.lineStaff : styles.lineJianPu,
  232. ]}
  233. style={{ left: metronomeData.activeMetro.left }}></div>
  234. )}
  235. {!state.isReport &&
  236. !!item.multipleRestMeasures &&
  237. state.activeMeasureIndex == item.MeasureNumberXML && (
  238. <div class={styles.dotWrap}>{item.multipleRestMeasures}</div>
  239. )}
  240. <Transition
  241. name="centerTop"
  242. onAfterEnter={() => {
  243. scoreItem.show = false;
  244. }}
  245. >
  246. {scoreItem?.show && (
  247. <div
  248. class={styles.scoreItem}
  249. style={{ color: leveByScoreMeasureIcons[scoreItem.leve]?.color || "" }}
  250. >
  251. <img src={leveByScoreMeasureIcons[scoreItem.leve]?.icon} />
  252. <span>{scoreItem.score}</span>
  253. </div>
  254. )}
  255. </Transition>
  256. </div>
  257. )}
  258. </>
  259. );
  260. })}
  261. {selectData.notes.map((item: any) => {
  262. return (
  263. <div
  264. class={[styles.position, disableClickNote.value && styles.disable, styles.note, `noteIndex_${item.index}`]}
  265. style={item.bbox}
  266. onClick={() => skipNotePlay(item.index)}
  267. >
  268. <div class={styles.noteFollow} data-vf={"vf" + item.id}>
  269. <Icon name="success" />
  270. <Icon name="cross" />
  271. </div>
  272. </div>
  273. );
  274. })}
  275. {/* 移动模块 */}
  276. {/* {query.isMove == "1" && <MoveMusicScore />} */}
  277. </div>
  278. );
  279. },
  280. });