index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. import { defineComponent, onMounted, Ref, ref, Transition, watch } from 'vue'
  2. import request from '/src/helpers/request'
  3. import originRequest from 'umi-request'
  4. import MusicSheet from '/src/music-sheet'
  5. import runtime from '/src/pages/detail/runtime'
  6. import {
  7. formatXML,
  8. onlyVisible,
  9. getAllNodes,
  10. getCustomInfo,
  11. getBoundingBoxByverticalNote,
  12. getParentNote,
  13. } from '/src/pages/detail/helpers'
  14. import SettingState from '/src/pages/detail/setting-state'
  15. import detailState from '/src/pages/detail/state'
  16. import { useOriginSearch } from '../colexiu/uses'
  17. import styles from '../colexiu/index.module.less'
  18. import detailStyles from './index.module.less'
  19. import { OpenSheetMusicDisplay } from '/osmd-extended/src'
  20. import { MusicSheelDetail, ShaeetStatusType } from '../colexiu/index.d'
  21. import Header, { active } from './header'
  22. import { colorsClass } from '/src/pages/report'
  23. import { getLeveByScoreMeasure } from '/src/pages/detail/evaluating/helper'
  24. import { Button, Skeleton } from 'vant'
  25. import Empty from '/src/components/empty'
  26. import { useSpecialShapedScreen } from '../colexiu/uses/use-app'
  27. import { postMessage, promisefiyPostMessage } from '/src/helpers/native-message'
  28. const search = useOriginSearch()
  29. const useXml = async (url: string, detail: MusicSheelDetail) => {
  30. const xml = await originRequest(url)
  31. let score = ref<string>('')
  32. const parseXmlInfo = getCustomInfo(xml)
  33. score.value = formatXML(parseXmlInfo.parsedXML, {
  34. title: detail.musicSheetName,
  35. })
  36. const partIndex = Number(search['part-index']) || 0
  37. score.value = onlyVisible(score.value, partIndex)
  38. return score
  39. }
  40. const useDetail = (id: number | string): [Ref<ShaeetStatusType>, Ref<MusicSheelDetail>, Ref<any>] => {
  41. const status = ref<ShaeetStatusType>('loading')
  42. const data = ref<MusicSheelDetail>({})
  43. const record = ref<any>({})
  44. onMounted(async () => {
  45. // 没有token
  46. const token = sessionStorage.getItem('Authorization')
  47. console.log('第一次请求', token)
  48. if (!token) {
  49. // 获取token
  50. const res = await promisefiyPostMessage({ api: 'getToken' })
  51. if (res?.content?.accessToken) {
  52. sessionStorage.setItem('Authorization', res.content.tokenType + ' ' + res.content.accessToken)
  53. }
  54. }
  55. status.value = 'loading'
  56. try {
  57. const recordRes = await request.get('/musicPracticeRecord/getLastEvaluationMusicalNotesPlayStats', {
  58. params: {
  59. recordId: search.id,
  60. },
  61. })
  62. if (!recordRes.data) {
  63. status.value = 'error'
  64. return
  65. }
  66. record.value = recordRes.data
  67. const res = await request.get(`/musicSheet/detail/${record.value?.musicalNotesPlayStats.examSongId}`)
  68. data.value = res.data
  69. detailState.partIndex = recordRes.data.partIndex || 0
  70. detailState.isPercussion = res.data?.background?.[detailState.partIndex]?.musicSubject == 1
  71. status.value = 'success'
  72. } catch (error) {
  73. status.value = 'error'
  74. console.log(error)
  75. }
  76. })
  77. return [status, data, record]
  78. }
  79. export default defineComponent({
  80. name: 'Colexiu',
  81. setup() {
  82. const headerRef = ref()
  83. const renderLoading = ref(true)
  84. const renderError = ref(false)
  85. const score = ref<string>('')
  86. const useedid = ref<string[]>([])
  87. const allNote = ref<any[]>([])
  88. const [detailStatus, detail, record] = useDetail(search.id as string)
  89. watch(detailStatus, async () => {
  90. if (detailStatus.value === 'success' && detail.value.xmlFileUrl) {
  91. const xml = await useXml(detail.value.xmlFileUrl, detail.value)
  92. // console.log(runtime.songs, detailState.partListNames)
  93. score.value = xml.value
  94. }
  95. })
  96. // useUser()
  97. useSpecialShapedScreen()
  98. const getOffsetPosition = (type: keyof typeof colorsClass): string => {
  99. switch (type) {
  100. case 'CADENCE_FAST':
  101. return 'translateX(2px)'
  102. case 'CADENCE_SLOW':
  103. return 'translateX(-2px)'
  104. case 'INTONATION_HIGH':
  105. return 'translateY(-2px)'
  106. case 'INTONATION_LOW':
  107. return 'translateY(2px)'
  108. default:
  109. return ''
  110. }
  111. }
  112. const filterNotes = () => {
  113. const include = ['RIGHT', 'WRONG', 'CADENCE_WRONG']
  114. console.log(active.value)
  115. if (active.value === 'pitch') {
  116. include.push(...['CADENCE_FAST', 'CADENCE_SLOW'])
  117. } else if (active.value === 'rhythm') {
  118. include.push(...['INTONATION_HIGH', 'INTONATION_LOW'])
  119. } else if (active.value === 'completion') {
  120. include.push(...['INTEGRITY_WRONG'])
  121. }
  122. return record.value.musicalNotesPlayStats.notesData.filter((item: any) => include.includes(item.musicalErrorType))
  123. }
  124. const setViewColor = () => {
  125. clearViewColor()
  126. for (const note of filterNotes()) {
  127. const active = allNote.value[note.musicalNotesIndex]
  128. console.log("🚀 ~ active:", active)
  129. setTimeout(() => {
  130. if (useedid.value.includes(active.id)) {
  131. return
  132. }
  133. useedid.value.push(active.id)
  134. const svgEl = document.getElementById('vf-' + active.id)
  135. const stemEl = document.getElementById('vf-' + active.id + '-stem')
  136. const errType = note.musicalErrorType as keyof typeof colorsClass
  137. const isNeedCopyElement = ['INTONATION_HIGH', 'INTONATION_LOW', 'CADENCE_FAST', 'CADENCE_SLOW'].includes(
  138. errType
  139. )
  140. stemEl?.classList.add(colorsClass[errType])
  141. svgEl?.classList.add(colorsClass[errType])
  142. if (svgEl && isNeedCopyElement) {
  143. stemEl?.classList.remove(colorsClass[errType])
  144. stemEl?.classList.add(colorsClass.RIGHT)
  145. svgEl?.classList.remove(colorsClass[errType])
  146. svgEl?.classList.add(colorsClass.RIGHT)
  147. const copySvg = svgEl.querySelector('.vf-notehead')!.cloneNode(true) as SVGSVGElement
  148. copySvg.style.transform = getOffsetPosition(errType)
  149. svgEl.style.opacity = '.7'
  150. if (stemEl) {
  151. stemEl.style.opacity = '.7'
  152. }
  153. copySvg.id = 'vf-' + active.id + '-copy'
  154. copySvg?.classList.add(colorsClass[errType])
  155. // stemEl?.classList.add(colorsClass.RIGHT)
  156. // @ts-ignore
  157. osmd?.container.querySelector('svg')!.insertAdjacentElement('afterbegin', copySvg)
  158. // svgEl?.parentElement?.appendChild(copySvg)
  159. }
  160. }, 300)
  161. }
  162. }
  163. const removeClass = (el?: HTMLElement | null) => {
  164. if (!el) return
  165. const classList = el.classList.values()
  166. for (const val of classList) {
  167. if (val?.indexOf('vf-') !== 0) {
  168. el.classList.remove(val)
  169. }
  170. }
  171. }
  172. const clearViewColor = () => {
  173. for (const id of useedid.value) {
  174. removeClass(document.getElementById('vf-' + id))
  175. removeClass(document.getElementById('vf-' + id + '-stem'))
  176. const qid = 'vf-' + id + '-copy'
  177. const copyEl = document.getElementById(qid)
  178. if (copyEl) {
  179. copyEl.remove()
  180. }
  181. }
  182. useedid.value = []
  183. }
  184. const onRerender = (osmd: OpenSheetMusicDisplay) => {
  185. renderLoading.value = false
  186. headerRef.value?.autoShow()
  187. setTimeout(() => {
  188. for (const item of Array.from(document.querySelectorAll('.vf-beam'))) {
  189. ;(item as SVGAElement).querySelector('path')?.setAttribute('fill', '#aeaeae')
  190. }
  191. })
  192. runtime.osmd = osmd
  193. allNote.value = getAllNodes(runtime.osmd)
  194. console.log("🚀 ~ allNote.value:", allNote.value)
  195. setViewColor()
  196. const setEvaluatings = (note: any, data: any, dontTransition = true) => {
  197. const startNote = getBoundingBoxByverticalNote(note)
  198. console.log("🚀 ~ startNote:", startNote)
  199. detailState.evaluatings = {
  200. ...detailState.evaluatings,
  201. [startNote.measureIndex]: {
  202. ...startNote,
  203. ...getLeveByScoreMeasure(data.score),
  204. score: data.score,
  205. dontTransition,
  206. },
  207. }
  208. }
  209. if (record.value.userMeasureScore) {
  210. for (const key in record.value.userMeasureScore) {
  211. if (Object.prototype.hasOwnProperty.call(record.value.userMeasureScore, key)) {
  212. const data = record.value.userMeasureScore[key]
  213. for (const time of allNote.value) {
  214. if (data.measureRenderIndex == time.noteElement.sourceMeasure.MeasureNumberXML - 1) {
  215. if (!time.noteElement.tie) {
  216. setEvaluatings(time, data)
  217. } else {
  218. for (const item of time.noteElement.tie.notes) {
  219. const note = getParentNote(item)
  220. if (!note) continue
  221. setEvaluatings(note, data, item !== time.noteElement.tie.StartNote)
  222. }
  223. }
  224. }
  225. }
  226. }
  227. }
  228. }
  229. // console.log(detailState.evaluatings, record.value.userMeasureScore)
  230. // detailState.activeDetail.originalSpeed = osmd.Sheet.userStartTempoInBPM
  231. // RuntimeUtils.setAudioInit()
  232. }
  233. const onRenderError = () => {
  234. renderError.value = true
  235. renderLoading.value = false
  236. }
  237. return () => {
  238. const loading = renderLoading.value || detailStatus.value === 'loading'
  239. const error = renderError.value || detailStatus.value === 'error'
  240. // console.log('ColexiuRender', detail.value.musicSubject, score.value)
  241. return (
  242. <div
  243. class={[
  244. styles.container,
  245. SettingState.sett.eyeProtection && 'eyeProtection',
  246. SettingState.sett.camera && 'openCamera',
  247. ]}
  248. >
  249. {!renderLoading.value && (
  250. <Header
  251. className={styles.header}
  252. detail={detail.value}
  253. record={record}
  254. ref={headerRef}
  255. style={{
  256. paddingLeft: detailState.isSpecialShapedScreen ? detailState.notchHeight / 2 + 'px' : 'auto',
  257. }}
  258. onActiveChange={(active: string) => setViewColor()}
  259. />
  260. )}
  261. <div
  262. id="colexiu-detail-music-sheet"
  263. class={[styles.musicSheet, detailStyles.musicSheet]}
  264. style={{
  265. paddingLeft: detailState.isSpecialShapedScreen ? detailState.notchHeight / 2 + 'px' : 'auto',
  266. }}
  267. >
  268. {loading && !error && <Skeleton class={styles.skeleton} rowWidth="80%" title row={3} />}
  269. {error && <Empty />}
  270. {score.value && (
  271. <>
  272. <div class={styles.headTitle}>{detail.value.musicSheetName}</div>
  273. <MusicSheet
  274. score={score.value}
  275. showSection
  276. opotions={{
  277. drawTitle: false,
  278. drawComposer: false,
  279. drawLyricist: false,
  280. drawMetronomeMarks: true,
  281. drawMeasureNumbers: true,
  282. autoResize: false,
  283. }}
  284. EngravingRules={{
  285. DefaultColorNotehead: '#aeaeae',
  286. DefaultColorRest: '#aeaeae',
  287. DefaultColorMusic: '#aeaeae',
  288. DefaultColorStem: '#aeaeae',
  289. DefaultColorChordSymbol: '#aeaeae',
  290. DefaultColorLabel: '#aeaeae',
  291. DYMusicScoreType: SettingState.sett.type,
  292. }}
  293. onRerender={onRerender}
  294. onRenderError={onRenderError}
  295. />
  296. </>
  297. )}
  298. </div>
  299. </div>
  300. )
  301. }
  302. },
  303. })