index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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. setTimeout(() => {
  129. if (useedid.value.includes(active.id)) {
  130. return
  131. }
  132. useedid.value.push(active.id)
  133. const svgEl = document.getElementById('vf-' + active.id)
  134. const stemEl = document.getElementById('vf-' + active.id + '-stem')
  135. const errType = note.musicalErrorType as keyof typeof colorsClass
  136. const isNeedCopyElement = ['INTONATION_HIGH', 'INTONATION_LOW', 'CADENCE_FAST', 'CADENCE_SLOW'].includes(
  137. errType
  138. )
  139. stemEl?.classList.add(colorsClass[errType])
  140. svgEl?.classList.add(colorsClass[errType])
  141. if (svgEl && isNeedCopyElement) {
  142. stemEl?.classList.remove(colorsClass[errType])
  143. stemEl?.classList.add(colorsClass.RIGHT)
  144. svgEl?.classList.remove(colorsClass[errType])
  145. svgEl?.classList.add(colorsClass.RIGHT)
  146. const copySvg = svgEl.querySelector('.vf-notehead')!.cloneNode(true) as SVGSVGElement
  147. copySvg.style.transform = getOffsetPosition(errType)
  148. svgEl.style.opacity = '.7'
  149. if (stemEl) {
  150. stemEl.style.opacity = '.7'
  151. }
  152. copySvg.id = 'vf-' + active.id + '-copy'
  153. copySvg?.classList.add(colorsClass[errType])
  154. // stemEl?.classList.add(colorsClass.RIGHT)
  155. // @ts-ignore
  156. osmd?.container.querySelector('svg')!.insertAdjacentElement('afterbegin', copySvg)
  157. // svgEl?.parentElement?.appendChild(copySvg)
  158. }
  159. }, 300)
  160. }
  161. }
  162. const removeClass = (el?: HTMLElement | null) => {
  163. if (!el) return
  164. const classList = el.classList.values()
  165. for (const val of classList) {
  166. if (val?.indexOf('vf-') !== 0) {
  167. el.classList.remove(val)
  168. }
  169. }
  170. }
  171. const clearViewColor = () => {
  172. for (const id of useedid.value) {
  173. removeClass(document.getElementById('vf-' + id))
  174. removeClass(document.getElementById('vf-' + id + '-stem'))
  175. const qid = 'vf-' + id + '-copy'
  176. const copyEl = document.getElementById(qid)
  177. if (copyEl) {
  178. copyEl.remove()
  179. }
  180. }
  181. useedid.value = []
  182. }
  183. const onRerender = (osmd: OpenSheetMusicDisplay) => {
  184. renderLoading.value = false
  185. headerRef.value?.autoShow()
  186. setTimeout(() => {
  187. for (const item of Array.from(document.querySelectorAll('.vf-beam'))) {
  188. ;(item as SVGAElement).querySelector('path')?.setAttribute('fill', '#aeaeae')
  189. }
  190. })
  191. runtime.osmd = osmd
  192. allNote.value = getAllNodes(runtime.osmd)
  193. setViewColor()
  194. const setEvaluatings = (note: any, data: any, dontTransition = true) => {
  195. const startNote = getBoundingBoxByverticalNote(note)
  196. detailState.evaluatings = {
  197. ...detailState.evaluatings,
  198. [startNote.measureIndex]: {
  199. ...startNote,
  200. ...getLeveByScoreMeasure(data.score),
  201. score: data.score,
  202. dontTransition,
  203. },
  204. }
  205. }
  206. if (record.value.userMeasureScore) {
  207. for (const key in record.value.userMeasureScore) {
  208. if (Object.prototype.hasOwnProperty.call(record.value.userMeasureScore, key)) {
  209. const data = record.value.userMeasureScore[key]
  210. for (const time of allNote.value) {
  211. if (data.measureRenderIndex == time.noteElement.sourceMeasure.MeasureNumberXML - 1) {
  212. if (!time.noteElement.tie) {
  213. setEvaluatings(time, data)
  214. } else {
  215. for (const item of time.noteElement.tie.notes) {
  216. const note = getParentNote(item)
  217. if (!note) continue
  218. setEvaluatings(note, data, item !== time.noteElement.tie.StartNote)
  219. }
  220. }
  221. }
  222. }
  223. }
  224. }
  225. }
  226. // console.log(detailState.evaluatings, record.value.userMeasureScore)
  227. // detailState.activeDetail.originalSpeed = osmd.Sheet.userStartTempoInBPM
  228. // RuntimeUtils.setAudioInit()
  229. }
  230. const onRenderError = () => {
  231. renderError.value = true
  232. renderLoading.value = false
  233. }
  234. return () => {
  235. const loading = renderLoading.value || detailStatus.value === 'loading'
  236. const error = renderError.value || detailStatus.value === 'error'
  237. // console.log('ColexiuRender', detail.value.musicSubject, score.value)
  238. return (
  239. <div
  240. class={[
  241. styles.container,
  242. SettingState.sett.eyeProtection && 'eyeProtection',
  243. ]}
  244. >
  245. {!renderLoading.value && (
  246. <Header
  247. className={styles.header}
  248. detail={detail.value}
  249. record={record}
  250. ref={headerRef}
  251. style={{
  252. paddingLeft: detailState.isSpecialShapedScreen ? detailState.notchHeight / 2 + 'px' : 'auto',
  253. }}
  254. onActiveChange={(active: string) => setViewColor()}
  255. />
  256. )}
  257. <div
  258. id="colexiu-detail-music-sheet"
  259. class={[styles.musicSheet, detailStyles.musicSheet]}
  260. style={{
  261. paddingLeft: detailState.isSpecialShapedScreen ? detailState.notchHeight / 2 + 'px' : 'auto',
  262. }}
  263. >
  264. {loading && !error && <Skeleton class={styles.skeleton} rowWidth="80%" title row={3} />}
  265. {error && <Empty />}
  266. {score.value && (
  267. <>
  268. <div class={styles.headTitle}>{detail.value.musicSheetName}</div>
  269. <MusicSheet
  270. score={score.value}
  271. showSection
  272. opotions={{
  273. drawTitle: false,
  274. drawComposer: false,
  275. drawLyricist: false,
  276. drawMetronomeMarks: true,
  277. drawMeasureNumbers: true,
  278. autoResize: false,
  279. }}
  280. EngravingRules={{
  281. DefaultColorNotehead: '#aeaeae',
  282. DefaultColorRest: '#aeaeae',
  283. DefaultColorMusic: '#aeaeae',
  284. DefaultColorStem: '#aeaeae',
  285. DefaultColorChordSymbol: '#aeaeae',
  286. DefaultColorLabel: '#aeaeae',
  287. DYMusicScoreType: SettingState.sett.type,
  288. }}
  289. onRerender={onRerender}
  290. onRenderError={onRenderError}
  291. />
  292. </>
  293. )}
  294. </div>
  295. </div>
  296. )
  297. }
  298. },
  299. })