index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import { computed, defineComponent, onMounted, reactive, Transition, nextTick, watch } 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, showToast } 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. measureHeight: 0 as number, // 小节高度
  15. });
  16. /** 计算点击层数据 */
  17. const calcNoteData = () => {
  18. const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || {
  19. x: 0,
  20. y: 0,
  21. };
  22. const parentLeft = musicContainer.x || 0;
  23. const parentTop = musicContainer.y || 0;
  24. const notes = state.times;
  25. const notesList: string[] = [];
  26. const MeasureNumberXMLList: number[] = [];
  27. let minMeasureHeigt: number = 0;
  28. for (let i = 0; i < notes.length; i++) {
  29. const item = notes[i];
  30. // console.log("🚀 ~ item:", item)
  31. const noteItem = {
  32. ...item,
  33. index: item.i,
  34. bbox: null as any,
  35. staveBox: null as any,
  36. };
  37. if (!notesList.includes(item.noteId)) {
  38. let staveBbox: any = {};
  39. if (item.stave?.attrs?.id) {
  40. const staveEle = document.querySelector(`#${item.stave.attrs.id}`);
  41. staveBbox = staveEle?.parentElement?.parentElement?.getBoundingClientRect?.() || {
  42. x: 0,
  43. width: 0,
  44. };
  45. // console.log("🚀 ~ staveBbox:", staveBbox.height)
  46. }
  47. if (item.svgElement) {
  48. const noteEle = document.querySelector(`#vf-${item.svgElement?.attrs?.id}`);
  49. if (noteEle) {
  50. const noteBbox = noteEle.getBoundingClientRect?.() || { x: 0, width: 0 };
  51. if (state.musicRenderType !== EnumMusicRenderType.staff) {
  52. noteItem.bbox = {
  53. left: noteBbox.x - parentLeft - noteBbox.width / 4 + "px",
  54. top: noteBbox.y - parentTop - noteBbox.height + "px",
  55. width: noteBbox.width * 1.5 + "px",
  56. height: noteBbox.height * 3 + "px",
  57. };
  58. const noteHead = noteEle.querySelector(".vf-numbered-note-head");
  59. const noteHeadBbox = noteHead?.getBoundingClientRect?.();
  60. if (noteHeadBbox) {
  61. item.bbox = {
  62. left: noteHeadBbox.x - parentLeft - noteHeadBbox.width / 4,
  63. width: noteHeadBbox.width * 1.5,
  64. }
  65. }
  66. } else {
  67. noteItem.bbox = {
  68. left: noteBbox.x - parentLeft - noteBbox.width / 4 + "px",
  69. top: staveBbox.y - parentTop + "px",
  70. width: noteBbox.width * 1.5 + "px",
  71. height: staveBbox.height + "px",
  72. };
  73. }
  74. }
  75. if (selectData.notes.find((item:any) => item.id === noteItem.id)) {
  76. //
  77. } else {
  78. selectData.notes.push(noteItem);
  79. }
  80. // selectData.notes.push(noteItem);
  81. notesList.push(item.noteId);
  82. }
  83. }
  84. if (!MeasureNumberXMLList.includes(item.MeasureNumberXML)) {
  85. if (item.stave) {
  86. if (item.stave?.attrs?.id) {
  87. const staveEle = document.querySelector(`#${item.stave.attrs.id}`);
  88. const list = [
  89. Array.from(staveEle?.querySelectorAll(".vf-clef") || []),
  90. Array.from(staveEle?.querySelectorAll(".vf-keysignature") || []),
  91. Array.from(staveEle?.getElementsByTagName("text") || []),
  92. ].flat();
  93. try {
  94. if (list.length) {
  95. // console.log("🚀 ~ list:", list)
  96. list.forEach((_el: any) => {
  97. _el?.style?.setProperty("display", "none");
  98. });
  99. }
  100. } catch (error) {}
  101. const staveBbox = staveEle?.getBoundingClientRect?.() || { x: 0, width: 0, y: 0, height: 0 };
  102. if (i === 0) {
  103. minMeasureHeigt = staveBbox.height
  104. }
  105. try {
  106. if (list.length) {
  107. list.forEach((_el: any) => {
  108. _el?.style?.removeProperty("display");
  109. });
  110. }
  111. } catch (error) {}
  112. // console.log("🚀 ~ staveEle:", staveBbox.height)
  113. selectData.measureHeight = staveBbox.height
  114. let compareVal = staveBbox.height - minMeasureHeigt
  115. compareVal = compareVal > 0 ? compareVal : 0
  116. noteItem.staveBox = {
  117. left: staveBbox.x - parentLeft + "px",
  118. // top: ((item.stave.y || 0) - 5) * state.zoom + "px",
  119. top: staveBbox.y - parentTop + compareVal + "px",
  120. width: staveBbox.width + "px",
  121. height: staveBbox.height - compareVal + "px",
  122. // background: 'rgba(0,0,0,.2)'
  123. };
  124. selectData.staves.push(noteItem);
  125. }
  126. MeasureNumberXMLList.push(item.MeasureNumberXML);
  127. } else {
  128. if (item.multipleRestMeasures) {
  129. const preItem = selectData.staves.find(
  130. (n: any) => n.MeasureNumberXML === item.MeasureNumberXML - 1
  131. );
  132. if (preItem?.staveBox) {
  133. noteItem.staveBox = {
  134. left: preItem.staveBox.left,
  135. top: preItem.staveBox.top,
  136. width: preItem.staveBox.width,
  137. // height: preItem.staveBox.height,
  138. };
  139. selectData.staves.push(noteItem);
  140. MeasureNumberXMLList.push(item.MeasureNumberXML);
  141. }
  142. }
  143. }
  144. }
  145. }
  146. console.log("🚀 ~ selectData.notes:", selectData.notes, selectData.staves);
  147. };
  148. /** 重新计算 */
  149. export const recalculateNoteData = () => {
  150. selectData.notes = [];
  151. selectData.staves = [];
  152. calcNoteData();
  153. };
  154. export default defineComponent({
  155. name: "selection",
  156. setup() {
  157. const route = useRoute();
  158. const query: any = {
  159. ...getQuery(),
  160. ...route.query,
  161. };
  162. /** 是否可以点击音符 */
  163. const disableClickNote = computed(() => {
  164. return state.sectionStatus || state.modeType !== "practise";
  165. });
  166. // 选段符号
  167. const sectionPosData = computed(() => {
  168. if(state.sectionStatus) {
  169. return state.section.map(((item,index) => {
  170. if(index === 0){
  171. const currItem = selectData.staves.find(stave => {
  172. return stave.MeasureNumberXML === item.MeasureNumberXML
  173. })
  174. return currItem && {
  175. left: currItem.staveBox.left,
  176. top: currItem.staveBox.top,
  177. height: selectData.measureHeight + 'px' // 小节的高度
  178. }
  179. } else {
  180. // 实际的结束位置
  181. const actualEndIndex = state.userChooseEndIndex > item.MeasureNumberXML ? state.userChooseEndIndex : item.MeasureNumberXML
  182. const currItem = selectData.staves.find(stave => {
  183. return stave.MeasureNumberXML === actualEndIndex
  184. })
  185. return currItem && {
  186. left: parseFloat(currItem.staveBox.left)+parseFloat(currItem.staveBox.width)+"px",
  187. top: currItem.staveBox.top,
  188. height: selectData.measureHeight + 'px'
  189. }
  190. }
  191. }))
  192. }
  193. return []
  194. })
  195. onMounted(() => {
  196. selectData.notes = [];
  197. selectData.staves = [];
  198. calcNoteData();
  199. // 初始化谱面可移动的元素位置
  200. try {
  201. moveData.partIndex = state.partIndex + ""
  202. nextTick(() => renderForMoveData())
  203. } catch (error) {}
  204. });
  205. return () => (
  206. <div
  207. id="selectionBox"
  208. class={[
  209. styles.selectionContainer,
  210. ]}
  211. onClick={(e: Event) => e.stopPropagation()}
  212. >
  213. {selectData.staves.map((item: any) => {
  214. const scoreItem = item.id && evaluatingData.evaluatings[item.measureListIndex];
  215. // for(let idx in evaluatingData.evaluatings) {
  216. // const { show, measureIndex } = evaluatingData.evaluatings[idx]
  217. // if (show && measureIndex !== item.measureListIndex) {
  218. // evaluatingData.evaluatings[idx].show = false
  219. // }
  220. // }
  221. // 高级模式下,显示节拍线
  222. // 不是报告模式
  223. // 不是多小节休止符
  224. // 节拍线开关
  225. // 当前小节
  226. // 当前小节
  227. const lineShow =
  228. !state.isReport &&
  229. metronomeData.cursorMode === 2 &&
  230. item.MeasureNumberXML === metronomeData.activeMetro?.measureNumberXML &&
  231. state.times[state.activeNoteIndex].MeasureNumberXML === item.MeasureNumberXML;
  232. return (
  233. <>
  234. {item.staveBox && (
  235. <div
  236. class={[
  237. styles.position,
  238. scoreItem ? `scoreItemLeve${scoreItem.leve}` : "",
  239. item.multipleRestMeasures <= 1 ? styles.staveBg : "",
  240. (state.platform === IPlatform.PC && state.zoom > 0.8) ? styles.linePC : '',
  241. ]}
  242. style={item.staveBox}
  243. onClick={() => handleSelection(item)}
  244. >
  245. {lineShow && (
  246. <div style={{height: selectData.measureHeight + 'px', position: 'relative'}}>
  247. <div
  248. class={[
  249. styles.line,
  250. state.setting.eyeProtection ? styles.eyeLine : '',
  251. state.musicRenderType == EnumMusicRenderType.staff ? styles.lineStaff : styles.lineJianPu,
  252. ]}
  253. style={{ left: metronomeData.activeMetro.left }}></div>
  254. </div>
  255. )}
  256. {!state.isReport &&
  257. !!item.multipleRestMeasures &&
  258. state.activeMeasureIndex == item.MeasureNumberXML && (
  259. <div class={styles.dotWrap}>{item.multipleRestMeasures}</div>
  260. )}
  261. <Transition
  262. name="centerTop"
  263. onAfterEnter={() => {
  264. scoreItem.show = false;
  265. }}
  266. >
  267. {scoreItem?.show && (
  268. <div
  269. class={styles.scoreItem}
  270. style={{ color: leveByScoreMeasureIcons[scoreItem.leve]?.color || "" }}
  271. >
  272. <img src={leveByScoreMeasureIcons[scoreItem.leve]?.icon} />
  273. <span>{scoreItem.score}</span>
  274. </div>
  275. )}
  276. </Transition>
  277. </div>
  278. )}
  279. </>
  280. );
  281. })}
  282. {selectData.notes.map((item: any) => {
  283. return (
  284. <div
  285. class={[styles.position, disableClickNote.value && styles.disable, styles.note, `noteIndex_${item.index}`]}
  286. style={item.bbox}
  287. onClick={() => skipNotePlay(item.index)}
  288. >
  289. <div class={styles.noteFollow} data-vf={"vf" + item.id}>
  290. <Icon name="success" />
  291. <Icon name="cross" />
  292. </div>
  293. <div class={[styles.noteDot, 'node-dot']}></div>
  294. </div>
  295. );
  296. })}
  297. {/* 选段 */}
  298. {
  299. sectionPosData.value.map((item,index) =>{
  300. return (
  301. item && <div class={styles.selectBox} style={item}>
  302. <div class={[styles.selectHandle,index>0&&styles.selectHandleRight,state.playState==="play"&&styles.playIng]} onClick={()=>{
  303. // 如果选择了2个 删除左边的时候
  304. if(state.section.length===2&&index === 0){
  305. state.section = []
  306. showToast({
  307. message: "请选择开始小节",
  308. duration: 0,
  309. position: "top",
  310. className: "selectionToast",
  311. });
  312. }else{
  313. state.section.splice(index,1)
  314. state.section = [...state.section] // 触发 watch
  315. showToast({
  316. message: state.section.length?"请选择结束小节":"请选择开始小节",
  317. duration: 0,
  318. position: "top",
  319. className: "selectionToast",
  320. });
  321. }
  322. }}></div>
  323. </div>
  324. )
  325. })
  326. }
  327. {/* 移动模块 */}
  328. {query.isMove == "1" && <MoveMusicScore />}
  329. </div>
  330. );
  331. },
  332. });