index.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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, { moveData, renderForMoveData } 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.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. const actualEndIndex = state.userChooseEndIndex > state.section[1].MeasureNumberXML ? state.userChooseEndIndex : state.section[1].MeasureNumberXML
  163. // 选段预备拍背景
  164. if (state.sectionFirst && item.MeasureNumberXML === state.sectionFirst.MeasureNumberXML) {
  165. return styles.prepareStaveBox;
  166. }
  167. if (
  168. item.MeasureNumberXML >= state.section[0].MeasureNumberXML &&
  169. item.MeasureNumberXML <= actualEndIndex
  170. ) {
  171. if (
  172. item.MeasureNumberXML == state.section[0].MeasureNumberXML &&
  173. item.MeasureNumberXML == actualEndIndex
  174. ) {
  175. return styles.centerStaveBox;
  176. }
  177. if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
  178. return styles.leftStaveBox;
  179. }
  180. if (item.MeasureNumberXML == actualEndIndex) {
  181. return styles.rightStaveBox;
  182. }
  183. return styles.staveBox;
  184. }
  185. }
  186. } else {
  187. if (state.activeMeasureIndex == item.MeasureNumberXML && !state.isReport) {
  188. return styles.staveBox;
  189. }
  190. }
  191. };
  192. });
  193. onMounted(() => {
  194. calcNoteData();
  195. // 初始化谱面可移动的元素位置
  196. try {
  197. moveData.partIndex = query['part-index'] as string || '0'
  198. renderForMoveData()
  199. } catch (error) {}
  200. });
  201. return () => (
  202. <div
  203. id="selectionBox"
  204. class={styles.selectionContainer}
  205. onClick={(e: Event) => e.stopPropagation()}
  206. >
  207. {selectData.staves.map((item: any) => {
  208. const scoreItem = item.id && evaluatingData.evaluatings[item.measureListIndex];
  209. // for(let idx in evaluatingData.evaluatings) {
  210. // const { show, measureIndex } = evaluatingData.evaluatings[idx]
  211. // if (show && measureIndex !== item.measureListIndex) {
  212. // evaluatingData.evaluatings[idx].show = false
  213. // }
  214. // }
  215. // 高级模式下,显示节拍线
  216. // 不是报告模式
  217. // 不是多小节休止符
  218. // 节拍线开关
  219. // 当前小节
  220. // 当前小节
  221. const lineShow =
  222. !state.isReport &&
  223. !state.times[state.activeNoteIndex].multipleRestMeasures &&
  224. metronomeData.cursorMode === 2 &&
  225. item.MeasureNumberXML === metronomeData.activeMetro?.measureNumberXML &&
  226. state.times[state.activeNoteIndex].MeasureNumberXML === item.MeasureNumberXML;
  227. return (
  228. <>
  229. {item.staveBox && (
  230. <div
  231. class={[
  232. styles.position,
  233. showClass.value(item),
  234. scoreItem ? `scoreItemLeve${scoreItem.leve}` : "",
  235. state.platform === IPlatform.PC ? styles.linePC : ''
  236. ]}
  237. style={item.staveBox}
  238. onClick={() => handleSelection(item)}
  239. >
  240. {lineShow && (
  241. <div
  242. class={[
  243. styles.line,
  244. state.musicRenderType == EnumMusicRenderType.staff ? styles.lineStaff : styles.lineJianPu,
  245. ]}
  246. style={{ left: metronomeData.activeMetro.left }}></div>
  247. )}
  248. {!state.isReport &&
  249. !!item.multipleRestMeasures &&
  250. state.activeMeasureIndex == item.MeasureNumberXML && (
  251. <div class={styles.dotWrap}>{item.multipleRestMeasures}</div>
  252. )}
  253. <Transition
  254. name="centerTop"
  255. onAfterEnter={() => {
  256. scoreItem.show = false;
  257. }}
  258. >
  259. {scoreItem?.show && (
  260. <div
  261. class={styles.scoreItem}
  262. style={{ color: leveByScoreMeasureIcons[scoreItem.leve]?.color || "" }}
  263. >
  264. <img src={leveByScoreMeasureIcons[scoreItem.leve]?.icon} />
  265. <span>{scoreItem.score}</span>
  266. </div>
  267. )}
  268. </Transition>
  269. </div>
  270. )}
  271. </>
  272. );
  273. })}
  274. {selectData.notes.map((item: any) => {
  275. return (
  276. <div
  277. class={[styles.position, disableClickNote.value && styles.disable, styles.note, `noteIndex_${item.index}`]}
  278. style={item.bbox}
  279. onClick={() => skipNotePlay(item.index)}
  280. >
  281. <div class={styles.noteFollow} data-vf={"vf" + item.id}>
  282. <Icon name="success" />
  283. <Icon name="cross" />
  284. </div>
  285. </div>
  286. );
  287. })}
  288. {/* 移动模块 */}
  289. {query.isMove == "1" && <MoveMusicScore />}
  290. </div>
  291. );
  292. },
  293. });