123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- import { computed, defineComponent, onMounted, reactive, Transition, nextTick, watch } from "vue";
- import state, { EnumMusicRenderType, handleSelection, skipNotePlay, IPlatform, resetBaseRate } from "/src/state";
- import styles from "./index.module.less";
- import { metronomeData } from "/src/helpers/metronome";
- import { evaluatingData } from "../evaluating";
- import { leveByScoreMeasureIcons } from "../evaluating/evaluatResult";
- import { Icon, showToast } from "vant";
- import MoveMusicScore, { moveData, renderForMoveData } from "../plugins/move-music-score";
- import { useRoute } from "vue-router";
- import { getQuery } from "/src/utils/queryString";
- import IntonationDown from "./imgs/pitchLow.png"
- import IntonationUp from "./imgs/pitchHigh.png"
- import MultipleRestMeasures from "./multipleRestMeasures"
- import { browser } from "../../utils";
- import { transform } from "lodash";
- export default defineComponent({
- name: "selection",
- setup() {
- const browsInfo = browser();
- const isPad = navigator?.userAgent?.includes("UAWEIVRD-W09") || browsInfo?.iPad || browsInfo.isTablet;
- const route = useRoute();
- const query: any = {
- ...getQuery(),
- ...route.query,
- };
- const selectData = reactive({
- notes: [] as any[],
- staves: [] as any[],
- measureHeight: 0 as number, // 小节高度
- // beatWidth: '100%' as string, // 节拍指针占用的宽度,带有拍号的小节,节拍指针占用的宽度需要减去拍号左侧的宽度
- // beatLeft: 0 as number, // 小节有拍号时,拍号左侧宽度
- });
- const beatMeasureWidths: any = {}; // 节拍指针需要占用的宽度
- /** 计算点击层数据 */
- const calcNoteData = () => {
- const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || {
- x: 0,
- y: 0,
- };
- const parentLeft = musicContainer.x || 0;
- const parentTop = musicContainer.y || 0;
- const notes = state.times;
- const notesList: string[] = [];
- const MeasureNumberXMLList: number[] = [];
- let minMeasureHeigt: number = 0;
- for (let i = 0; i < notes.length; i++) {
- const item = notes[i];
- // console.log("🚀 ~ item:", item)
- const noteItem = {
- ...item,
- index: item.i,
- bbox: null as any,
- staveBox: null as any,
- };
- if (!notesList.includes(item.noteId)) {
- let staveBbox: any = {}, customBgBox: any = {};
- if (item.stave?.attrs?.id) {
- const staveEle = document.querySelector(`#${item.stave.attrs.id}`);
- staveBbox = staveEle?.parentElement?.parentElement?.getBoundingClientRect?.() || {
- x: 0,
- width: 0,
- };
- customBgBox = staveEle?.querySelector('.vf-custom-bg')?.getBoundingClientRect() || { y: 0, height: 0 }
- // console.log("🚀 ~ staveBbox:", staveBbox.height)
- }
- if (item.svgElement) {
- const noteEle = document.querySelector(`#vf-${item.svgElement?.attrs?.id}`);
- if (noteEle) {
- const noteBbox = noteEle.getBoundingClientRect?.() || { x: 0, width: 0 };
- if (state.musicRenderType !== EnumMusicRenderType.staff) {
- noteItem.bbox = {
- left: noteBbox.x - parentLeft - noteBbox.width / 4 + "px",
- top: noteBbox.y - parentTop - noteBbox.height + "px",
- width: noteBbox.width * 1.5 + "px",
- height: noteBbox.height * 3 + "px",
- x: item.bbox?.x,
- y: item.bbox?.y,
- originWidth: item.bbox?.width
- };
- const noteHead = noteEle.querySelector(".vf-numbered-note-head");
- const noteHeadBbox = noteHead?.getBoundingClientRect?.();
- if (noteHeadBbox) {
- item.bbox = {
- left: noteHeadBbox.x - parentLeft - noteHeadBbox.width / 4,
- width: noteHeadBbox.width * 1.5,
- x: item.bbox?.x,
- y: item.bbox?.y,
- originWidth: item.bbox?.width
- }
- }
-
- } else {
- const needTransY = -(staveBbox.height - customBgBox.height) / 2 + "px";
- noteItem.bbox = {
- left: noteBbox.x - parentLeft - noteBbox.width / 4 + "px",
- top: customBgBox.y ? customBgBox.y - parentTop + "px" : staveBbox.y - parentTop + "px",
- width: noteBbox.width * 1.5 + "px",
- height: staveBbox.height + "px",
- x: item.bbox?.x,
- y: item.bbox?.y,
- originWidth: item.bbox?.width,
- transform: `translateY(${needTransY})`
- };
- }
-
- }
- if (selectData.notes.find((item:any) => item.id === noteItem.id)) {
- //
- } else {
- selectData.notes.push(noteItem);
- }
- // selectData.notes.push(noteItem);
- notesList.push(item.noteId);
- }
- }
- // 如果小节里面有拍号,需要减去拍号左侧的宽度
- let beatWidth = '100%';
- let beatLeft = 0;
- if (!MeasureNumberXMLList.includes(item.MeasureNumberXML)) {
- if (item.stave) {
- if (item.stave?.attrs?.id) {
- const staveEle = document.querySelector(`#${item.stave.attrs.id}`);
- const list = [
- Array.from(staveEle?.querySelectorAll(".vf-clef") || []),
- Array.from(staveEle?.querySelectorAll(".vf-keysignature") || []),
- Array.from(staveEle?.getElementsByTagName("text") || []),
- ].flat();
- try {
- if (list.length) {
- // console.log("🚀 ~ list:", list)
- list.forEach((_el: any) => {
- _el?.style?.setProperty("display", "none");
- });
- }
- } catch (error) {}
-
- const staveBbox = staveEle?.getBoundingClientRect?.() || { x: 0, width: 0, y: 0, height: 0 };
- const timesignatureDom = staveEle?.querySelector('.vf-timesignature')
- if (timesignatureDom) {
- const timesignatureBbox = timesignatureDom.getBoundingClientRect()
- const leftWidth = timesignatureBbox.x + timesignatureBbox.width - staveBbox.x
- beatLeft = leftWidth
- beatWidth = `calc(100% - ${leftWidth+'px'})`
- }
- if (i === 0) {
- minMeasureHeigt = staveBbox.height
- }
- try {
- if (list.length) {
- list.forEach((_el: any) => {
- _el?.style?.removeProperty("display");
- });
- }
- } catch (error) {}
-
- // console.log("🚀 ~ staveEle:", staveBbox.height)
- selectData.measureHeight = staveBbox.height
- let compareVal = staveBbox.height - minMeasureHeigt
- compareVal = compareVal > 0 ? compareVal : 0
- selectData.measureHeight = staveBbox.height - compareVal
- noteItem.staveBox = {
- left: staveBbox.x - parentLeft + "px",
- // top: ((item.stave.y || 0) - 5) * state.zoom + "px",
- top: staveBbox.y - parentTop + compareVal + "px",
- width: staveBbox.width + "px",
- height: staveBbox.height - compareVal + "px",
- // background: 'rgba(0,0,0,.2)'
- };
- selectData.staves.push(noteItem);
- }
- MeasureNumberXMLList.push(item.MeasureNumberXML);
- beatMeasureWidths[item.MeasureNumberXML] = {
- beatLeft,
- beatWidth
- }
- } else {
- if (item.multipleRestMeasures) {
- if (state.isCombineRender) {
- let currentItem = null;
- for (let index = 0; index < state.vfmeasures.length; index++) {
- const element = state.vfmeasures[index];
- const measureNum = element.getAttribute('data-num') ? Number(element.getAttribute('data-num')) : -1;
- const nextMeasureNum = state.vfmeasures[index+1]?.getAttribute('data-num') ? Number(state.vfmeasures[index+1]?.getAttribute('data-num')) : -1;
- if (measureNum === item.MeasureNumberXML || item.MeasureNumberXML < nextMeasureNum || nextMeasureNum == -1) {
- currentItem = element
- break;
- }
- }
- const staveBbox = currentItem?.querySelector('.vf-stave')?.getBoundingClientRect() || { x: 0, width: 0, y: 0, height: 0 };
- if (currentItem) {
- noteItem.staveBox = {
- left: staveBbox.x - parentLeft + "px",
- // top: ((item.stave.y || 0) - 5) * state.zoom + "px",
- top: staveBbox.y - parentTop + "px",
- width: staveBbox.width + "px",
- height: staveBbox.height + "px",
- // height: preItem.staveBox.height,
- };
- selectData.staves.push(noteItem);
- MeasureNumberXMLList.push(item.MeasureNumberXML);
- beatMeasureWidths[item.MeasureNumberXML] = {
- beatLeft,
- beatWidth
- }
- }
- } else {
- const preItem = selectData.staves.find(
- (n: any) => n.MeasureNumberXML === item.MeasureNumberXML - 1
- );
- if (preItem?.staveBox) {
- noteItem.staveBox = {
- left: preItem.staveBox.left,
- top: preItem.staveBox.top,
- width: preItem.staveBox.width,
- // height: preItem.staveBox.height,
- };
- selectData.staves.push(noteItem);
- MeasureNumberXMLList.push(item.MeasureNumberXML);
- beatMeasureWidths[item.MeasureNumberXML] = {
- beatLeft,
- beatWidth
- }
- }
- }
-
- }
- }
- }
- }
- // 部分浏览器渲染的第一小节的位置信息会包含拍号、调号,需要处理一下,剔除掉拍号、调号的位置
- if (selectData.staves[0]?.staveBox?.top !== selectData.staves[1]?.staveBox?.top) {
- selectData.staves[0].staveBox.top = selectData.staves[1]?.staveBox?.top || selectData.staves[0]?.staveBox?.top
- }
- console.log("🚀 ~ selectData.notes:", selectData.notes, selectData.staves,beatMeasureWidths,MeasureNumberXMLList);
- };
- /** 是否可以点击音符 */
- const disableClickNote = computed(() => {
- return (state.sectionStatus && state.section.length != 2) || (state.modeType === "evaluating");
- });
- // 选段符号
- const sectionPosData = computed(() => {
- if(state.sectionStatus) {
- return state.section.map(((item,index) => {
- if(index === 0){
- const currItem = selectData.staves.find(stave => {
- return stave.MeasureNumberXML === item.MeasureNumberXML
- })
- // 获取stave里面vf-custom-bg的位置坐标,才是准确的坐标
- // const currBgX = document.getElementById(currItem.stave.attrs.id)?.querySelector('.vf-custom-bg')?.getBoundingClientRect()?.x || 0;
- const currBgX = currItem.stave?.attrs && currItem.stave.attrs.id ? document.getElementById(currItem.stave.attrs.id)?.querySelector('.vf-custom-bg')?.getBBox()?.x * state.zoom || 0 : 0;
- return currItem && {
- left: currBgX ? currBgX + 'px' : currItem.staveBox.left,
- top: currItem.staveBox.top,
- height: selectData.measureHeight + 'px' // 小节的高度
- }
- } else {
- // 实际的结束位置
- const actualEndIndex = state.userChooseEndIndex > item.MeasureNumberXML ? state.userChooseEndIndex : item.MeasureNumberXML
- const currItem = selectData.staves.find(stave => {
- return stave.MeasureNumberXML === actualEndIndex
- })
- return currItem && {
- left: parseFloat(currItem.staveBox.left)+parseFloat(currItem.staveBox.width)-2 +"px",
- top: currItem.staveBox.top,
- height: selectData.measureHeight + 'px'
- }
- }
- }))
- }
- return []
- })
- onMounted(() => {
- selectData.notes = [];
- selectData.staves = [];
- calcNoteData();
- const img: HTMLElement = document.querySelector('#cursorImg-0')!
- if (metronomeData.cursorMode === 2){
- img.classList.add('lineHide')
- } else {
- img.classList.remove('lineHide')
- }
- // 初始化谱面可移动的元素位置
- try {
- moveData.partIndex = state.partIndex + ""
- // 速度标记元素和谱面并非同时渲染,初始化可移动元素的时候,需要加个延迟
- setTimeout(() => {
- renderForMoveData()
- }, 0);
- } catch (error) {}
- });
- return () => (
- <>
- <div
- id="selectionBox"
- class={[
- styles.selectionContainer,
- isPad && styles.isPad,
- state.zoom == 1.25 ? styles.middleZoom : state.zoom == 1.5 ? styles.bigZoom : state.zoom == 1.75 ? styles.largeZoom : state.zoom == 2 ? styles.largeZoom2 : state.zoom == 2.25 ? styles.largeZoom3 :
- state.zoom == 0.65 ? styles.smallZoom : state.zoom == 0.5 ? styles.litteZoom : ''
- ]}
- onClick={(e: Event) => e.stopPropagation()}
- >
- {selectData.staves.map((item: any, index) => {
- // 评测得分
- const scoreItem = item.id && evaluatingData.evaluatings[item.measureListIndex];
- // for(let idx in evaluatingData.evaluatings) {
- // const { show, measureIndex } = evaluatingData.evaluatings[idx]
- // if (show && measureIndex !== item.measureListIndex) {
- // evaluatingData.evaluatings[idx].show = false
- // }
- // }
- // 高级模式下,显示节拍线
- // 不是报告模式
- // 不是多小节休止符
- // 节拍线开关
- // 当前小节
- // 当前小节
- /* 节拍指针,现在没有节拍器指针了,但是以后要加上的话,这里需要性能优化。现在这样每次节拍指针更新都会刷新这里的虚拟dom */
- const lineShow =
- !state.isReport &&
- metronomeData.cursorMode === 2 &&
- item.MeasureNumberXML === metronomeData.activeMetro?.measureNumberXML &&
- state.times[state.activeNoteIndex].MeasureNumberXML === item.MeasureNumberXML;
- //console.log('显示节拍指针',lineShow,state.times[state.activeNoteIndex].MeasureNumberXML,item.MeasureNumberXML,metronomeData.activeMetro?.measureNumberXML)
- return (
- <>
- {item.staveBox && (
- <div
- key={item.id}
- class={[
- styles.position,
- // scoreItem ? `scoreItemLeve${scoreItem.leve}` : "", // 去掉评测小节得分的背景色
- (state.platform === IPlatform.PC && state.zoom > 0.8) ? styles.linePC : '',
- `measureIndex_${item.MeasureNumberXML}`
- ]}
- style={item.staveBox}
- onClick={() => {
- // 当为连续休止小节的结束选段的时候 应该传休止小节 结束的位置
- let staveItem = item
- if(state.section.length === 1 && item.totalMultipleRestMeasures > 0){
- staveItem = selectData.staves[index + item.totalMultipleRestMeasures - 1]
- }
- handleSelection(staveItem)
- }}
- >
- {lineShow && (
- <div style={{height: selectData.measureHeight + 'px', position: 'relative', width: beatMeasureWidths[item.MeasureNumberXML].beatWidth, left: beatMeasureWidths[item.MeasureNumberXML].beatLeft + 'px'}}>
- <div
- class={[
- styles.line,
- state.setting.eyeProtection ? styles.eyeLine : '',
- state.musicRenderType == EnumMusicRenderType.staff ? styles.lineStaff : styles.lineJianPu,
- ]}
- style={{ left: metronomeData.activeMetro.left }}></div>
- </div>
- )}
- {!state.isReport &&
- !!item.multipleRestMeasures &&
- <MultipleRestMeasures item = {item}></MultipleRestMeasures>
- }
- <Transition
- name="centerTop"
- onAfterEnter={() => {
- scoreItem.show = false;
- }}
- >
- {scoreItem?.show && (
- <div
- class={styles.scoreItem}
- style={{ color: leveByScoreMeasureIcons[scoreItem.leve]?.color || "" }}
- >
- <img src={leveByScoreMeasureIcons[scoreItem.leve]?.icon} />
- <span>{scoreItem.score}</span>
- </div>
- )}
- </Transition>
- </div>
- )}
- </>
- );
- })}
- {selectData.notes.map((item: any) => {
- return (
- <div
- class={[styles.position, disableClickNote.value && styles.disable, styles.note, `noteIndex_${item.index}`]}
- style={item.bbox}
- onClick={() => skipNotePlay(item.index, false, 'manual')}
- >
- {/* <div class={styles.noteFollow} data-vf={"vf" + item.id}>
- <Icon name="success" />
- <Icon name="cross" />
- </div> */}
- <div class={styles.noteFollow} data-vf={"vf" + item.id}>
- {/* <Icon name="success" />
- <Icon name="cross" /> */}
- <div class={[styles.followTipUp, 'tip-up']}>
- <img src={IntonationUp} />
- {/* <span>音准<i>高了</i></span> */}
- </div>
- <div class={[styles.followTipDown, 'tip-down']}>
- <img src={IntonationDown} />
- {/* <span>音准<i>低了</i></span> */}
- </div>
- </div>
- <div class={[styles.noteDot, 'node-dot']}></div>
- </div>
- );
- })}
- {/* 选段 */}
- {
- sectionPosData.value.map((item,index) =>{
- return (
- item && <div class={styles.selectBox} style={item}>
- <div class={[styles.selectHandle,index>0&&styles.selectHandleRight,(state.playState==="play" || state.isHomeWork)&&styles.playIng]} onClick={()=>{
- // 如果选择了2个 删除左边的时候
- if (state.section.length===1&&index === 0) {
- // #bug:11552
- resetBaseRate(state.activeNoteIndex);
- }
- if(state.section.length===2&&index === 0){
- state.section = []
- // 重置速度和播放倍率
- resetBaseRate(state.activeNoteIndex);
- showToast({
- message: "请选择开始小节",
- duration: 0,
- position: "top",
- className: "selectionToast",
- });
- }else{
- state.section.splice(index,1)
- state.section = [...state.section] // 触发 watch
- showToast({
- message: state.section.length?"请选择结束小节":"请选择开始小节",
- duration: 0,
- position: "top",
- className: "selectionToast",
- });
- }
- // IOS18.1.1浏览器渲染更新有问题,需要手动更新一下
- const selectionDom = document.getElementById('selectionBox')
- if (selectionDom) {
- selectionDom.style.display = 'none';
- requestAnimationFrame(() => {
- selectionDom.style.display = 'block';
- })
- }
- }}></div>
- </div>
- )
- })
- }
- {/* 移动模块 */}
- {query.isMove == "1" && <MoveMusicScore />}
- </div>
- </>
- );
- },
- });
|