import { Skeleton } from "vant"; import { defineComponent, onBeforeMount, onBeforeUnmount, onMounted, reactive, Transition, watch, ref } from "vue"; import { formateTimes } from "../../helpers/formateMusic"; import state, { isRhythmicExercises, getMusicDetail, EnumMusicRenderType } from "../../state"; import { setGlobalData } from "../../utils"; import MusicScore from "../../view/music-score"; import styles from "./index.module.less"; import { api_cloudLoading, api_setStatusBarVisibility, isSpecialShapedScreen } from "/src/helpers/communication"; import { getQuery } from "/src/utils/queryString"; import { mappingVoicePart, subjectFingering } from "/src/view/fingering/fingering-config"; import { api_musicPracticeRecordDetail, sysMusicScoreAccompanimentQueryPage } from "../api"; import { getMusicSheetDetail } from "/src/utils/baseApi"; import ShareTop from "./component/share-top"; import { addMeasureScore } from "/src/view/evaluating"; import TopArrow from "./component/note/topArrow"; import BottomArrow from "./component/note/bottomArrow"; import LeftArrow from "./component/note/leftArrow"; import RightArrow from "./component/note/rightArrow"; const colorsClass: any = { RIGHT: styles.right, // 正确 WRONG: styles.wrong, // 错误 NOT_PLAYED: styles.notPlay, // 未演奏 EARLY: styles.cadence_fast, // 节奏快 LATE: styles.cadence_slow, // 节奏慢 HIGH: styles.intonation_high, // 音准高 LOW: styles.intonation_low, // 音准低 SHORT: styles.integrity_wrong, // 完整性(时值)不足 }; // const colorsClass: any = { // /** 音准 */ // pitch: { // /** 高了 */ // HIGH: styles.intonation_high, // /** 正常 */ // RIGHT: styles.intonation_right, // /** 低了 */ // LOW: styles.intonation_low, // /** 未演奏 */ // NOT_PLAYED: styles.notPlay, // /** 错误 */ // WRONG: styles.intonation_wrong, // /** 时值不足 */ // DURATION_INSUFFICIENT: styles.integrity_wrong // }, // /** 节奏 */ // rhythmic: { // /** 过早 */ // EARLY: styles.cadence_fast, // /** 正常 */ // RIGHT: styles.cadence_right, // /** 过迟 */ // LATE: styles.cadence_slow, // /** 未演奏 */ // NOT_PLAYED: styles.notPlay, // /** 错误 */ // WRONG: styles.cadence_wrong // } // } export default defineComponent({ name: "music-list", setup() { const query: any = getQuery(); const useedid = ref([]); const allNote = ref([]); const scoreData = reactive({ videoFilePath: "", // 回放视频路径 cadence: 0, integrity: 0, intonation: 0, score: 0, speed: 0, heardLevel: "", itemType: "intonation", musicType: "staff", }); const detailData = reactive({ isLoading: true, paddingLeft: "", headerHide: false, musicalNotesPlayStats: [] as any[], userMeasureScore: {} as any, isSpecialReport: false, // 是否是特殊的评测记录(含有listent、play等曲子的评测记录,非选段状态下可能返回的评测数据不是从第1小节开始的,这种情况需要兼容处理) }); const getAPPData = async () => { const screenData = await isSpecialShapedScreen(); if (screenData?.content) { const { isSpecialShapedScreen, notchHeight } = screenData.content; if (isSpecialShapedScreen) { detailData.paddingLeft = 25 + "px"; } } // 普通webview 没有获取异性屏的方法 detailData.paddingLeft = 20 + "px"; }; onBeforeMount(() => { getAPPData(); api_setStatusBarVisibility(); }); // console.log(route.params, query) /** 获取曲谱数据 */ const getMusicInfo = (res: any) => { const index = state.partIndex; const musicInfo = { ...res.data, ...res.data.background[index], }; // console.log("🚀 ~ musicInfo:", musicInfo); setState(musicInfo, index); setCustom(); detailData.isLoading = false; }; const setState = (data: any, index: number) => { // console.log("🚀 ~ data:", data) state.scrollContainer = "scrollContainer"; state.detailId = data.id; state.xmlUrl = data.xmlFileUrl; state.partIndex = index; state.subjectId = data.musicSubject; state.categoriesId = data.categoriesId; state.categoriesName = data.musicTagNames; state.enableEvaluation = data.canEvaluate ? true : false; state.examSongId = data.id + ""; state.examSongName = data.musicSheetName; // 解析扩展字段 if (data.extConfigJson) { try { state.extConfigJson = JSON.parse(data.extConfigJson as string); } catch (error) { console.error("解析扩展字段错误:", error); } } state.isOpenMetronome = data.mp3Type === "MP3_METRONOME" ? true : false; state.needTick = data.isOpenMetronome; state.isShowFingering = data.showFingering ? true : false; state.music = data.audioFileUrl; state.accompany = data.metronomeUrl || data.metronomeUrl; state.midiUrl = data.midiUrl; state.parentCategoriesId = data.musicTag; state.playMode = data.audioType === "MP3" ? "MP3" : "MIDI"; state.originSpeed = state.speed = data.speed; state.track = data.track; state.enableNotation = data.notation ? true : false; // 映射声部ID state.subjectId = mappingVoicePart(state.subjectId as any, "ORCHESTRA"); // console.log("🚀 ~ state.subjectId:", state.subjectId); // 是否打击乐 state.isPercussion = state.subjectId == 23 || state.subjectId == 113 || state.subjectId == 121 || isRhythmicExercises(); // 设置指法 state.fingeringInfo = subjectFingering(state.subjectId); // console.log("🚀 ~ state.fingeringInfo:", state.fingeringInfo, state.subjectId, state.track) // state.isOpenPrepare = true }; const setCustom = () => { if (state.extConfigJson.multitrack) { setGlobalData("multitrack", state.extConfigJson.multitrack); } }; onMounted(async () => { state.isEvaluatReport = true; const res = await api_musicPracticeRecordDetail(query.id); state.partIndex = Number(res?.data?.partIndex); let resultData = {} as any; try { resultData = JSON.parse(res?.data?.scoreData); } catch (error) { console.error("解析评测结果:", error); } // console.log("🚀 ~ resultData:", resultData); // @ts-ignore // resultData.musicalNotesPlayStats?.notesData.forEach((item) => item.rhythmicAssessment.result = 'EARLY') console.log('结果11',resultData) detailData.musicalNotesPlayStats = resultData.musicalNotesPlayStats?.notesData || []; detailData.userMeasureScore = resultData.userMeasureScore || {}; scoreData.heardLevel = res.data?.heardLevel; scoreData.cadence = res.data?.cadence; scoreData.integrity = res.data?.integrity; scoreData.intonation = res.data?.intonation; scoreData.score = res.data?.score; scoreData.speed = res.data?.speed; scoreData.videoFilePath = res.data?.videoFilePath || res.data?.recordFilePath; await getMusicDetail(resultData.musicalNotesPlayStats?.examSongId); // 从练习记录进入评测报告,默认显示五线谱 // if (!query.musicRenderType) { // state.musicRenderType = EnumMusicRenderType.staff // } // 评测报告展示什么类型的谱面 state.isSingleLine = false; scoreData.musicType = query.musicRenderType ? query.musicRenderType : resultData.musicType ? resultData.musicType : state.musicRenderType; // @ts-ignore state.musicRenderType = scoreData.musicType; detailData.isLoading = false; // Promise.all([ // getMusicSheetDetail(resultData.musicalNotesPlayStats?.examSongId), // ]).then((values) => { // getMusicInfo(values[0]); // }); }); const getOffsetPosition = (type: keyof typeof colorsClass): string => { // 五线谱 if (scoreData.musicType === "staff") { switch (type) { case "EARLY": return "translateX(-3px)"; case "LATE": return "translateX(3px)"; case "HIGH": return "translateY(-2px)"; case "LOW": return "translateY(2px)"; default: return ""; } } else { switch (type) { case "EARLY": return "translateX(-3px)"; case "LATE": return "translateX(3px)"; case "HIGH": return "translateY(-2px)"; case "LOW": return "translateY(-10px)"; default: return ""; } } }; const filterNotes = () => { let include = ["RIGHT", "WRONG", "NOT_PLAYED"]; if (scoreData.itemType === "intonation") { // 音准 include.push(...["HIGH", "LOW"]); } else if (scoreData.itemType === "cadence") { // 节奏 include.push(...["EARLY", "LATE"]); } else if (scoreData.itemType === "integrity") { // 完整性 include = ["SHORT", "NORMAL", "NOT_PLAYED"]; } if (scoreData.itemType === "cadence") { return detailData.musicalNotesPlayStats.filter((item: any) => include.includes(item.rhythmicAssessment.result)); } else if (scoreData.itemType === "integrity") { return detailData.musicalNotesPlayStats.filter((item: any) => include.includes(item.integrityAssessment?.result)); } else { return detailData.musicalNotesPlayStats.filter((item: any) => { let result = item.pitchAssessment.result; // if (scoreData.itemType === "integrity") { // result = result === "HIGH" || result === "LOW" || result === "WRONG" ? "RIGHT" : result; // } return include.includes(result); }); } }; const setViewColor = () => { clearViewColor(); const notes = filterNotes(); // console.log(1111,notes) for (const note of notes) { const idx = note.musicalNotesIndex !== undefined ? note.musicalNotesIndex : note.index; let active = allNote.value[idx]; if (detailData.isSpecialReport) { active = allNote.value.find((item: any) => item.i === idx) } setTimeout(() => { if (useedid.value.includes(active.id)) { return; } useedid.value.push(active.id); const svgEl = document.getElementById("vf-" + active.id); const stemEl = document.getElementById("vf-" + active.id + "-stem"); let errType = scoreData.itemType === "cadence" ? note.rhythmicAssessment.result : scoreData.itemType === "integrity" ? note.integrityAssessment.result : note.pitchAssessment.result; // console.log(1111222,errType) /** * 新版小酷AI不需要在当前的音符复制出来一个音符,所以注释掉isNeedCopyElement和copySvg */ // const isNeedCopyElement = scoreData.itemType === "integrity" ? false : ["HIGH", "LOW", "EARLY", "LATE"].includes(errType); const isNeedCopyElement = false; // if (scoreData.itemType === "integrity") { // errType = errType = note.pitchAssessment.result === "HIGH" || note.pitchAssessment.result === "LOW" || note.pitchAssessment.result === "WRONG" ? "RIGHT" : errType; // } if (scoreData.itemType === "integrity") { errType = errType = note.integrityAssessment.result === "NORMAL" ? "RIGHT" : note.integrityAssessment.result === "SHORT" ? "SHORT" : errType; } stemEl?.classList.add(colorsClass[errType]); svgEl?.classList.add(colorsClass[errType]); // console.log(123456,'添加颜色',errType) // 评测过的音符,需要给小节添加背景色 // if (errType !== "NOT_PLAYED") { // const staveNote = svgEl?.parentNode?.parentNode?.querySelector(".vf-stave"); // if (staveNote) { // staveNote.querySelector(".vf-custom-bg")?.setAttribute("fill", "#132D4C"); // } // } if (svgEl && isNeedCopyElement) { stemEl?.classList.remove(colorsClass[errType]); svgEl?.classList.remove(colorsClass[errType]); let copySvg: any = null; // 五线谱 if (scoreData.musicType === "staff") { stemEl?.classList.add(colorsClass.RIGHT); svgEl?.classList.add(colorsClass.RIGHT); // copySvg = svgEl.querySelector(".vf-notehead")!.cloneNode(true) as SVGSVGElement; } else { //copySvg = svgEl.querySelector('.vf-numbered-note-head')!.cloneNode(true) as SVGSVGElement if (isNeedCopyElement) { svgEl?.classList.add(styles.inaccuracy); const targetId = errType === "HIGH" ? "topSvg" : errType === "LOW" ? "bottomSvg" : errType === "EARLY" ? "leftSvg" : errType === "LATE" ? "rightSvg" : ""; // copySvg = document.getElementById(targetId)!.cloneNode(true) as SVGSVGElement; const { width, height } = svgEl.getBoundingClientRect() || {}; // @ts-ignore let { x, y } = svgEl?.getBBox() || {}; x = errType === "HIGH" ? x + (width - 15) / 2 + 2 : errType === "LOW" ? x + (width - 15) / 2 + 2 : errType === "EARLY" ? x - Math.abs((width - 15) / 2) - 12 : errType === "LATE" ? x + width + 6 : x; y = errType === "HIGH" ? y - Math.abs((height - 10) / 2) - 10 : errType === "LOW" ? y + height + 8 : errType === "EARLY" ? y + (height - 10) / 2 : errType === "LATE" ? y + (height - 10) / 2 : y; copySvg.setAttribute("x", x); copySvg.setAttribute("y", y); } // console.log(x,y,copySvg.getBoundingClientRect()) // const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); // rect.setAttribute("x", 0 +'px'); // rect.setAttribute("y", 0+'px'); // rect.setAttribute("width", `50`); // rect.setAttribute("height", `50`); // rect.setAttribute("fill", "#FF4444"); // svgEl.prepend(rect); } if (scoreData.musicType === "staff") { // copySvg.style.transform = getOffsetPosition(errType); if (stemEl) { // } } // copySvg.id = "vf-" + active.id + "-copy"; // copySvg?.classList.add(colorsClass[errType]); // @ts-ignore // state.osmd?.container.querySelector("svg")!.insertAdjacentElement("afterbegin", copySvg); } }, 300); } }; const removeClass = (el?: HTMLElement | null) => { if (!el) return; const classList = el.classList.values(); for (const val of classList) { if (val?.indexOf("vf-") !== 0) { el.classList.remove(val); } } }; const clearViewColor = () => { for (const id of useedid.value) { removeClass(document.getElementById("vf-" + id)); removeClass(document.getElementById("vf-" + id + "-stem")); const qid = "vf-" + id + "-copy"; const copyEl = document.getElementById(qid); if (copyEl) { copyEl.remove(); } } useedid.value = []; }; const setPathColor = () => { console.log(11111, detailData.musicalNotesPlayStats, scoreData.itemType); for (const note of detailData.musicalNotesPlayStats) { const active = allNote.value[note.index]; const svgEl = active?.id ? document.getElementById("vf-" + active?.id) : null; switch (scoreData.itemType) { case "intonation": svgEl?.classList.add(colorsClass.pitch[note.pitchAssessment.result]); break; case "cadence": svgEl?.classList.add(colorsClass.rhythmic[note.rhythmicAssessment.result]); break; case "integrity": svgEl?.classList.add(colorsClass.pitch[note.integrityAssessment.result]); break; default: break; } } }; const setMearureColor = () => { for (let key in detailData.userMeasureScore) { addMeasureScore(detailData.userMeasureScore[key], false); } }; /** 渲染完成 */ const handleRendered = (osmd: any) => { state.musicRendered = true; state.osmd = osmd; allNote.value = formateTimes(osmd); console.log("🚀 ~ state.times:", allNote.value); // @ts-ignore const startMeasureNum = detailData.musicalNotesPlayStats?.[0]?.measureRenderIndex, endMeasureNum = detailData.musicalNotesPlayStats?.last()?.measureRenderIndex; allNote.value = allNote.value.filter((item: any) => (item.MeasureNumberXML >= startMeasureNum+1 && item.MeasureNumberXML <= endMeasureNum+1)) // @ts-ignore const beams = Array.from(new Set(document.getElementsByClassName("vf-beam"))); beams.forEach((item: any) => { item.classList.add(styles.beam); }); detailData.isSpecialReport = startMeasureNum > 0 && detailData.musicalNotesPlayStats?.[0]?.musicalNotesIndex != 0; //setPathColor(); setViewColor(); // setMearureColor(); api_cloudLoading(); }; watch( () => scoreData.itemType, () => { setViewColor(); } ); return () => (
{!state.musicRendered && (
)}
e.stopPropagation()}> {state.musicRendered && }
{/* 曲谱渲染 */} {!detailData.isLoading && } {
}
); }, });