index.tsx 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410
  1. import {
  2. computed,
  3. defineComponent,
  4. nextTick,
  5. onMounted,
  6. onUnmounted,
  7. reactive,
  8. ref,
  9. watch
  10. } from 'vue'
  11. import umiRequest from 'umi-request'
  12. import { useRoute, useRouter } from 'vue-router'
  13. import request from '@/helpers/request'
  14. import ColHeader from '@/components/col-header'
  15. import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  16. import {
  17. Button,
  18. Cell,
  19. CellGroup,
  20. Checkbox,
  21. Dialog,
  22. Icon,
  23. Image,
  24. Popup,
  25. RadioGroup,
  26. Sticky,
  27. Tag,
  28. Radio,
  29. Toast,
  30. Picker
  31. } from 'vant'
  32. import styles from './index.module.less'
  33. // import Item from '../list/item'
  34. import { useRect } from '@vant/use'
  35. import { Vue3Lottie } from 'vue3-lottie'
  36. import { getRandomKey, musicBuy } from '../music'
  37. import { getOssUploadUrl, state } from '@/state'
  38. // import { useEventTracking } from '@/helpers/hooks'
  39. import ColSticky from '@/components/col-sticky'
  40. import { browser, moneyFormat } from '@/helpers/utils'
  41. import { orderStatus } from '@/views/order-detail/orderStatus'
  42. import iconShare from '@/views/music/album/icon_share.svg'
  43. import iconAlbum from './images/icon_album.png'
  44. import iconAlbum2 from './images/icon_album2.png'
  45. import iconDownload from './images/icon_download.png'
  46. import iconChange from './images/icon-change.png'
  47. import iconAddCourse from './images/icon-add-course.png'
  48. import iconRemoveCourse from './images/icon-remove-course.png'
  49. import AstronautJSON from './animate/bigLoad.json'
  50. import ColShare from '@/components/col-share'
  51. import iconCollect from './images/icon_collect.png'
  52. import iconCollectActive from './images/icon_collect_active.png'
  53. import iconListen from './images/icon_listen.png'
  54. import iconTeacher from '@common/images/icon_teacher.png'
  55. import emtpy from './images/emtpy.png'
  56. import activeButtonIcon from '@common/images/icon_checkbox.png'
  57. import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
  58. import staffDetafult from './images/staff-default.png'
  59. // import staffActive from './images/staff-active.png'
  60. import firstDefault from './images/first-default.png'
  61. // import firstActive from './images/first-active.png'
  62. import fixedDefault from './images/fixed-default.png'
  63. // import fixedActive from './images/fixed-active.png'
  64. import Plyr from 'plyr'
  65. import 'plyr/dist/plyr.css'
  66. import Download from './download'
  67. import { getInstrumentName } from '@/constant/instruments'
  68. import { getUploadSign, onOnlyFileUpload } from '@/helpers/oss-file-upload'
  69. import { svgtopng } from '@/tenant/music/music-detail/formatSvgToImg'
  70. import { useThrottleFn } from '@vueuse/core'
  71. import {
  72. formatXML,
  73. getCustomInfo,
  74. onlyVisible
  75. } from '@/tenant/music/music-detail/instrument'
  76. export const getAssetsHomeFile = (fileName: string) => {
  77. const path = `../component/images/${fileName}`
  78. const modules = import.meta.globEager('../component/images/*')
  79. return modules[path].default
  80. }
  81. export default defineComponent({
  82. name: 'MusicDetail',
  83. setup() {
  84. localStorage.setItem('behaviorId', getRandomKey())
  85. const router = useRouter()
  86. const route = useRoute()
  87. const loading = ref(false)
  88. const aId = Number(route.query.activityId) || 0
  89. const studentActivityId = ref(aId)
  90. const isError = ref(false)
  91. const headers = ref(null)
  92. const footers = ref(null)
  93. const heightInfo = ref<any>('0')
  94. const musicDetail = ref<any>(null)
  95. const audioFileUrl = ref('')
  96. const showImg = ref([] as any)
  97. const firstList = ref<Array<any>>([])
  98. const fixedList = ref<Array<any>>([])
  99. const staffList = ref<Array<any>>([])
  100. const musicPdfUrl = ref('')
  101. const defaultImgs = ref({
  102. first: false,
  103. fixed: false,
  104. staff: false
  105. })
  106. const accompanyUrl = ref<string>('')
  107. const downloadStatus = ref<boolean>(false)
  108. const staff = reactive({
  109. status: false,
  110. radio: 'staff' // staff first fixed
  111. })
  112. const colors: any = {
  113. FREE: {
  114. color: '#01B84F',
  115. text: '免费'
  116. },
  117. VIP: {
  118. color: '#CD863E',
  119. text: '会员'
  120. },
  121. CHARGE: {
  122. color: '#3591CE',
  123. text: '点播'
  124. }
  125. }
  126. // 更改预览状态
  127. const onChangeStaff = (type: string) => {
  128. staff.radio = type
  129. staff.status = false
  130. if (type == 'first') {
  131. loading.value = false
  132. const tempPdf = musicDetail.value?.firstPdfUrl
  133. initIframe(tempPdf, 'first', staffData.musicXml)
  134. } else if (type == 'fixed') {
  135. loading.value = false
  136. const tempPdf = musicDetail.value?.jianPdfUrl
  137. console.log(tempPdf, 'tempPdf')
  138. initIframe(tempPdf, 'fixed', staffData.musicXml)
  139. } else {
  140. loading.value = false
  141. const tempPdf = musicDetail.value?.musicPdfUrl
  142. initIframe(tempPdf, 'staff', staffData.musicXml)
  143. }
  144. }
  145. const initIframe = (tempPdf: string, staff: string, xml: string) => {
  146. if (tempPdf) {
  147. musicPdfUrl.value = tempPdf
  148. renderStaff()
  149. } else {
  150. musicPdfUrl.value = ''
  151. // 为了处理,之前是使用pdf渲染,现在又用osmd,iframe没有重新加载
  152. if (
  153. !staffData.iframeSrc ||
  154. staffData.iframeSrc.indexOf('pdf/web') !== -1
  155. ) {
  156. renderStaff()
  157. } else {
  158. resetRenderPage(staff, xml)
  159. }
  160. }
  161. }
  162. watch(
  163. () => staff.radio,
  164. (val: string) => {
  165. if (val == 'first') {
  166. showImg.value = firstList.value
  167. } else if (val == 'fixed') {
  168. showImg.value = fixedList.value
  169. } else {
  170. showImg.value = staffList.value
  171. }
  172. }
  173. )
  174. const FetchList = async (id?: any) => {
  175. if (loading.value) {
  176. return
  177. }
  178. loading.value = true
  179. isError.value = false
  180. try {
  181. const res = await request.get(`/music/sheet/detail/${route.query.id}`, {
  182. prefix:
  183. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
  184. })
  185. musicDetail.value = res.data
  186. // console.log(musicDetail.value.notation, 'musicDetail')
  187. // 取原音,如果有多个则默认第一个
  188. const background = res.data.background
  189. audioFileUrl.value =
  190. background && background.length > 0 ? background[0].audioFileUrl : ''
  191. // const arrImgs = res.data.musicImg ? res.data.musicImg.split(',') : []
  192. showImg.value = res.data.musicImg ? res.data.musicImg.split(',') : []
  193. firstList.value = res.data.firstTone
  194. ? res.data.firstTone.split(',')
  195. : []
  196. fixedList.value = res.data.fixedTone
  197. ? res.data.fixedTone.split(',')
  198. : []
  199. staffList.value = res.data.musicImg ? res.data.musicImg.split(',') : []
  200. // 初始化默认数据是否有值
  201. if (firstList.value.length > 0) {
  202. defaultImgs.value.first = true
  203. }
  204. if (fixedList.value.length > 0) {
  205. defaultImgs.value.fixed = true
  206. }
  207. if (staffList.value.length > 0) {
  208. defaultImgs.value.staff = true
  209. }
  210. nextTick(async () => {
  211. await toDetail()
  212. renderStaff()
  213. })
  214. if (res.data.auditStatus === 'DOING') {
  215. Dialog.confirm({
  216. message: '曲目审核中',
  217. showConfirmButton: true,
  218. showCancelButton: false,
  219. confirmButtonColor: 'var(--van-primary)'
  220. }).then(() => {
  221. if (browser().isApp) {
  222. postMessage({ api: 'goBack' })
  223. } else {
  224. router.back()
  225. }
  226. })
  227. }
  228. } catch (error) {
  229. isError.value = true
  230. }
  231. if (musicDetail.value?.musicSheetType !== 'CONCERT') {
  232. loading.value = false
  233. }
  234. }
  235. const player = ref<any>(null)
  236. const audio = ref<any>(null)
  237. const freeRate = ref<any>(0)
  238. const initAudio = async () => {
  239. const controls = [
  240. 'play-large',
  241. 'play',
  242. 'progress',
  243. 'captions',
  244. // 'fullscreen',
  245. 'duration'
  246. ]
  247. player.value = new Plyr(audio.value, {
  248. controls: controls
  249. })
  250. const config = await request.get(
  251. '/api-student/sysConfig/queryByParamNameList',
  252. {
  253. params: {
  254. paramNames: 'music_sheet_free_rate'
  255. }
  256. }
  257. )
  258. freeRate.value = config.data[0]?.paramValue || 0
  259. player.value.on('timeupdate', () => {
  260. // 允许播放时间
  261. const players = player.value
  262. const playTime = (players.duration * freeRate.value) / 100 || 0
  263. // 时间,不能播放
  264. if (players.currentTime >= playTime && !buyState.value.play) {
  265. players.stop()
  266. // players.pause()
  267. }
  268. })
  269. }
  270. const showLoading = async (e: any) => {
  271. if (e.data?.api === 'musicStaffRender') {
  272. const osmdImg = e.data.osmdImg
  273. showImg.value = []
  274. const imgs: any = []
  275. for (let i = 0; i < osmdImg.length; i++) {
  276. const img = await svgtopng(
  277. osmdImg[i].img,
  278. osmdImg[i].width,
  279. osmdImg[i].height
  280. )
  281. imgs.push(img)
  282. }
  283. showImg.value = imgs
  284. loading.value = e.data.loading
  285. }
  286. }
  287. onMounted(async () => {
  288. postMessage({
  289. api: 'setStatusBarTextColor',
  290. content: { statusBarTextColor: true }
  291. })
  292. await FetchList()
  293. const { height } = useRect(headers as any)
  294. const footer = useRect(footers as any)
  295. heightInfo.value = height + footer.height
  296. // 初始化音频
  297. if (audioFileUrl.value) {
  298. initAudio()
  299. }
  300. window.addEventListener('message', showLoading)
  301. })
  302. onUnmounted(() => {
  303. postMessage({
  304. api: 'setStatusBarTextColor',
  305. content: { statusBarTextColor: false }
  306. })
  307. window.removeEventListener('message', showLoading)
  308. })
  309. const toggleFavorite = async () => {
  310. try {
  311. await request.post('/music/sheet/favorite/' + musicDetail.value?.id, {
  312. prefix:
  313. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
  314. })
  315. musicDetail.value.favorite = musicDetail.value?.favorite ? 0 : 1
  316. musicDetail.value.favoriteCount = musicDetail.value?.favorite
  317. ? musicDetail.value.favoriteCount + 1
  318. : musicDetail.value.favoriteCount - 1 < 0
  319. ? 0
  320. : musicDetail.value.favoriteCount - 1
  321. setTimeout(() => {
  322. Toast(musicDetail.value?.favorite ? '收藏成功' : '取消收藏成功')
  323. }, 100)
  324. } catch (error) {
  325. //
  326. }
  327. }
  328. const onAddCourse = async () => {
  329. try {
  330. const res = await request.post('/api-teacher/courseCourseware/submit', {
  331. data: {
  332. musicSheetId: musicDetail.value.id,
  333. clientType: 'TEACHER',
  334. userId: state.user.data?.userId
  335. }
  336. })
  337. // console.log(res)
  338. setTimeout(() => {
  339. musicDetail.value.coursewareId = res.data.id || ''
  340. Toast('已将曲目添加到课件')
  341. musicDetail.value.coursewareStatus = 1
  342. }, 100)
  343. } catch {
  344. //
  345. }
  346. }
  347. const removeCourse = async () => {
  348. Dialog.confirm({
  349. title: '提示',
  350. message: '您是否确定移出课件',
  351. confirmButtonColor: '#269a93',
  352. cancelButtonText: '取消',
  353. confirmButtonText: '确定'
  354. }).then(async () => {
  355. try {
  356. await request.post(
  357. '/api-teacher/courseCourseware/remove/' +
  358. musicDetail.value.coursewareId,
  359. {
  360. data: {}
  361. }
  362. )
  363. setTimeout(() => {
  364. Toast('移出成功')
  365. musicDetail.value.coursewareStatus = 0
  366. }, 100)
  367. } catch {
  368. //
  369. }
  370. })
  371. }
  372. const onBuy = async () => {
  373. const music = musicDetail.value
  374. orderStatus.orderObject.orderType = 'MUSIC'
  375. orderStatus.orderObject.orderName = music.musicSheetName
  376. orderStatus.orderObject.orderDesc = music.musicSheetName
  377. orderStatus.orderObject.actualPrice = music.musicPrice
  378. orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  379. orderStatus.orderObject.activityId = route.query.activityId || 0
  380. orderStatus.orderObject.orderNo = ''
  381. orderStatus.orderObject.orderList = [
  382. {
  383. orderType: 'MUSIC',
  384. goodsName: music.musicSheetName,
  385. actualPrice: music.musicPrice,
  386. ...music
  387. }
  388. ]
  389. const res = await request.post('/api-student/userOrder/getPendingOrder', {
  390. data: {
  391. goodType: 'MUSIC',
  392. bizId: music.id
  393. }
  394. })
  395. const result = res.data
  396. if (result) {
  397. Dialog.confirm({
  398. title: '提示',
  399. message: '您有一个未支付的订单,是否继续支付?',
  400. confirmButtonColor: '#269a93',
  401. cancelButtonText: '取消订单',
  402. confirmButtonText: '继续支付'
  403. })
  404. .then(async () => {
  405. orderStatus.orderObject.orderNo = result.orderNo
  406. orderStatus.orderObject.actualPrice = result.actualPrice
  407. orderStatus.orderObject.discountPrice = result.discountPrice
  408. orderStatus.orderObject.paymentConfig = {
  409. ...result.paymentConfig,
  410. paymentVendor: result.paymentVendor,
  411. paymentVersion: result.paymentVersion
  412. }
  413. routerTo()
  414. })
  415. .catch(() => {
  416. Dialog.close()
  417. // 只用取消订单,不用做其它处理
  418. cancelPayment(result.orderNo)
  419. })
  420. } else {
  421. routerTo()
  422. }
  423. }
  424. const routerTo = () => {
  425. const music = musicDetail.value
  426. router.push({
  427. path: '/orderDetail',
  428. query: {
  429. orderType: 'MUSIC',
  430. musicId: music.id
  431. }
  432. })
  433. }
  434. const cancelPayment = async (orderNo: string) => {
  435. try {
  436. await request.post('/api-student/userOrder/orderCancel', {
  437. data: {
  438. orderNo
  439. }
  440. })
  441. } catch {}
  442. }
  443. const paymentType = computed(() => {
  444. let paymentType = musicDetail.value?.paymentType
  445. if (typeof paymentType === 'string') {
  446. paymentType = paymentType.split(',')
  447. return paymentType
  448. }
  449. return []
  450. })
  451. const buyState = computed(() => {
  452. const music = musicDetail.value
  453. return {
  454. hasTenantAlbum: route.query?.tenantAlbumId ? true : false, // 是否从专辑来的
  455. play: music.play ? true : false, // 是否可以播放
  456. free: music?.paymentType.includes('FREE'),
  457. charge: music?.paymentType.includes('CHARGE'),
  458. vip: music?.paymentType.includes('VIP'),
  459. buy: music?.orderStatus === 'PAID' // 是否已买
  460. }
  461. })
  462. const shareStatus = ref(false)
  463. const shareUrl = ref('')
  464. const shareDiscount = ref(0)
  465. // console.log(data)
  466. const onShare = async () => {
  467. try {
  468. const res = await request.post('/api-teacher/open/musicShareProfit', {
  469. data: {
  470. bizId: musicDetail.value?.id,
  471. userId: state.user.data?.userId
  472. }
  473. })
  474. let url =
  475. location.origin +
  476. `/teacher/#/shareMusic?id=${musicDetail.value?.id}&recomUserId=${state.user.data?.userId}&userType=${state.platformType}`
  477. // 判断是否有活动
  478. if (res.data.discount === 1) {
  479. url += `&activityId=${res.data.activityId}`
  480. }
  481. shareDiscount.value = res.data.discount || 0
  482. console.log(url)
  483. shareUrl.value = url
  484. shareStatus.value = true
  485. return
  486. } catch {}
  487. }
  488. const staffData = reactive({
  489. open: false,
  490. iframeSrc: '',
  491. musicXml: '',
  492. instrumentName: '',
  493. iframeRef: null as any,
  494. partIndex: 0,
  495. partXmlIndex: 0,
  496. tempPartList: [] as any[],
  497. partList: [] as any[],
  498. xmlPartList: [] as any[]
  499. })
  500. /** 渲染五线谱 */
  501. const sortList = {
  502. 长笛: 1,
  503. 单簧管: 2,
  504. 中音单簧管: 3,
  505. 低音单簧管: 4,
  506. 高音萨克斯风: 5,
  507. 中音萨克斯风: 6,
  508. 次中音萨克斯风: 7,
  509. 低音萨克斯风: 8,
  510. 小号: 9,
  511. 长号: 10,
  512. 圆号: 11,
  513. 大号: 12,
  514. 上低音号: 13
  515. }
  516. const instrumentSort = (list: Array<any>) => {
  517. list.sort((a, b) => {
  518. return (
  519. (sortList[getInstrumentName(a.track)] || 20) -
  520. (sortList[getInstrumentName(b.track)] || 20)
  521. )
  522. })
  523. return list
  524. }
  525. const toDetail = async () => {
  526. try {
  527. if (musicDetail.value?.xmlFileUrl) {
  528. // 获取文件
  529. const res = await umiRequest.get(musicDetail.value?.xmlFileUrl, {
  530. mode: 'cors'
  531. })
  532. let partNames: string[] = []
  533. const xml: any = new DOMParser().parseFromString(res, 'text/xml')
  534. for (const item of xml.getElementsByTagName('part-name')) {
  535. if (item.textContent) {
  536. partNames.push(item.textContent?.trim())
  537. }
  538. }
  539. partNames = partNames.filter(
  540. (item: any) => !item?.toLocaleUpperCase()?.includes('COMMON')
  541. )
  542. const partList: any = []
  543. for (let j = 0; j < partNames.length; j++) {
  544. partList.push({
  545. name: partNames[j],
  546. value: j
  547. })
  548. }
  549. staffData.xmlPartList = partList
  550. }
  551. // staffData.iframeSrc = `${location.origin}/osmd/index.html`
  552. // staffData.iframeSrc = `${location.origin}${location.pathname}osmd/index.html`
  553. staffData.musicXml = musicDetail.value?.xmlFileUrl || ''
  554. const tempList = musicDetail.value?.background || []
  555. const tempPartList = [] as any
  556. staffData.xmlPartList.forEach((part: any) => {
  557. const item = tempList.find((item: any) => item.track === part.name)
  558. if (item) {
  559. tempPartList.push({
  560. ...item,
  561. index: part.value
  562. })
  563. }
  564. })
  565. staffData.partList = tempPartList
  566. staffData.tempPartList = JSON.parse(JSON.stringify(staffData.partList))
  567. staffData.partList = instrumentSort(staffData.partList)
  568. staffData.partXmlIndex = staffData.partList[0]?.index || 0
  569. staffData.instrumentName =
  570. musicDetail.value?.musicSheetType === 'CONCERT'
  571. ? getInstrumentName(staffData.partList[staffData.partIndex]?.track)
  572. : ''
  573. if (musicDetail.value?.musicSheetType === 'SINGLE') {
  574. musicPdfUrl.value = musicDetail.value?.musicPdfUrl
  575. } else {
  576. musicPdfUrl.value = staffData.partList[0]?.musicPdfUrl
  577. }
  578. } catch (error) {
  579. //
  580. console.log(error, 'error')
  581. }
  582. }
  583. const renderStaff = async () => {
  584. try {
  585. nextTick(() => {
  586. if (musicPdfUrl.value) {
  587. // const url = `${location.origin}/pdf/web/viewer-pdf.html?file=${encodeURIComponent(
  588. // musicPdfUrl.value
  589. // )}&t=${Date.now()}`
  590. const url = `${location.origin}${
  591. location.pathname
  592. }pdf/web/viewer-pdf.html?file=${encodeURIComponent(
  593. musicPdfUrl.value
  594. )}&t=${Date.now()}`
  595. const iframeRef = document.querySelector('#staffIframeRef') as any
  596. iframeRef.contentWindow.location.replace(url)
  597. staffData.iframeSrc = url
  598. } else {
  599. const url = `${location.origin}${
  600. location.pathname
  601. }osmd/index.html?t=${new Date().getTime()}`
  602. // const url = `${location.origin}/osmd/index.html`
  603. const iframeRef = document.querySelector('#staffIframeRef') as any
  604. iframeRef.contentWindow.location.replace(
  605. `${location.origin}${location.pathname}osmd/index.html`
  606. )
  607. staffData.iframeSrc = url
  608. }
  609. })
  610. } catch (error) {
  611. //
  612. }
  613. }
  614. const musicIframeLoad = async () => {
  615. const iframeRef: any = document.getElementById('staffIframeRef')
  616. if (iframeRef && iframeRef.contentWindow.renderXml) {
  617. const res = await umiRequest.get(staffData.musicXml, {
  618. mode: 'cors'
  619. })
  620. const parseXmlInfo = getCustomInfo(res)
  621. const xml = formatXML(parseXmlInfo.parsedXML)
  622. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  623. iframeRef.contentWindow.renderXml(currentXml, 0, staff.radio)
  624. // iframeRef.contentWindow.renderXml(
  625. // staffData.musicXml,
  626. // staffData.partXmlIndex
  627. // )
  628. }
  629. }
  630. const resetRender = async () => {
  631. const iframeRef: any = document.getElementById('staffIframeRef')
  632. if (iframeRef && iframeRef.contentWindow.renderXml) {
  633. loading.value = true
  634. // iframeRef.contentWindow.resetRender(staffData.partXmlIndex)
  635. const res = await umiRequest.get(staffData.musicXml, {
  636. mode: 'cors'
  637. })
  638. const parseXmlInfo = getCustomInfo(res)
  639. const xml = formatXML(parseXmlInfo.parsedXML)
  640. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  641. iframeRef.contentWindow.renderXml(currentXml, 0)
  642. staffData.instrumentName = getInstrumentName(
  643. staffData.partList[staffData.partIndex]?.track
  644. )
  645. }
  646. }
  647. const resetRenderPage = async (type: string, xmlUrl: string) => {
  648. const iframeRef: any = document.getElementById('staffIframeRef')
  649. if (iframeRef && iframeRef.contentWindow.renderXml) {
  650. loading.value = true
  651. const res = await umiRequest.get(staffData.musicXml, {
  652. mode: 'cors'
  653. })
  654. const parseXmlInfo = getCustomInfo(res)
  655. const xml = formatXML(parseXmlInfo.parsedXML)
  656. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  657. iframeRef.contentWindow.resetRenderPage(type, currentXml)
  658. }
  659. }
  660. const partColumns = computed(() => {
  661. return staffData.partList.map((item: any, index: number) => {
  662. const instrumentName =
  663. musicDetail.value?.musicSheetType === 'CONCERT'
  664. ? getInstrumentName(item.track)
  665. : ''
  666. return {
  667. text: item.track + (instrumentName ? `(${instrumentName})` : ''),
  668. value: index,
  669. instrumentName,
  670. musicPdfUrl: item.musicPdfUrl,
  671. firstPdfUrl: item.firstPdfUrl,
  672. jianPdfUrl: item.jianPdfUrl,
  673. xmlValue: item.index,
  674. track: item.track
  675. }
  676. })
  677. })
  678. return () => {
  679. return (
  680. <div class={styles.detail}>
  681. <ColSticky position="top">
  682. <div ref={headers}>
  683. <ColHeader
  684. background="transparent"
  685. hideHeader={false}
  686. border={false}
  687. isFixed={false}
  688. color="#fff"
  689. title={musicDetail.value?.musicSheetName}
  690. backIconColor="white"
  691. v-slots={{
  692. right: () => (
  693. <div
  694. class={styles.shareBtn}
  695. style={{
  696. color: '#fff'
  697. }}
  698. onClick={onShare}
  699. >
  700. <Image src={iconShare} />
  701. 分享
  702. </div>
  703. )
  704. }}
  705. />
  706. </div>
  707. </ColSticky>
  708. <img class={styles.bgImg} src={musicDetail.value?.titleImg} />
  709. <div class={styles.bgContent}></div>
  710. <div
  711. class={styles.musicContainer}
  712. style={{
  713. marginTop: '16px',
  714. height: `calc(100vh - var(--header-height) - var(--bottom-height) - 16px)`
  715. }}
  716. >
  717. <div>
  718. <Cell
  719. border={false}
  720. center
  721. class={styles.musicInfo}
  722. v-slots={{
  723. icon: () => (
  724. <Image
  725. class={styles.pImg}
  726. src={musicDetail.value?.titleImg}
  727. />
  728. ),
  729. title: () => (
  730. <div class={styles.info}>
  731. <h4
  732. class="van-ellipsis"
  733. // onClick={() => handleGotoMusicScore(musicDetail.value)}
  734. >
  735. {musicDetail.value?.musicSheetName}
  736. </h4>
  737. <p
  738. style={{
  739. display: 'flex',
  740. alignItems: 'center'
  741. }}
  742. >
  743. {paymentType.value.map(
  744. tag =>
  745. tag && (
  746. <Tag
  747. style={{ color: colors[tag]?.color }}
  748. class={styles.tag}
  749. type="success"
  750. plain
  751. >
  752. {colors[tag].text}
  753. </Tag>
  754. )
  755. )}
  756. {musicDetail.value?.exquisiteFlag === 1 && (
  757. <Image
  758. class={styles.exquisiteFlag}
  759. src={getAssetsHomeFile('icon_exquisite.png')}
  760. />
  761. )}
  762. {musicDetail.value?.albumNums > 0 && (
  763. <Image
  764. class={styles.songAlbum}
  765. src={getAssetsHomeFile('icon_album_active.png')}
  766. />
  767. )}
  768. <span
  769. class={[
  770. styles.coomposer,
  771. browser().isApp &&
  772. musicDetail.value?.sourceType === 'TEACHER' &&
  773. state.platformType === 'STUDENT' &&
  774. styles.links
  775. ]}
  776. onClick={() => {
  777. if (
  778. browser().isApp &&
  779. musicDetail.value?.sourceType === 'TEACHER' &&
  780. state.platformType === 'STUDENT'
  781. ) {
  782. router.push({
  783. path: '/teacherHome',
  784. query: {
  785. teacherId: musicDetail.value?.userId,
  786. tabs: 'music'
  787. }
  788. })
  789. }
  790. }}
  791. >
  792. <img class={styles.iconAlbum2} src={iconAlbum2} />
  793. <span>
  794. {musicDetail.value?.userName ||
  795. '游客' + (musicDetail.value?.userId || '')}
  796. </span>
  797. </span>
  798. </p>
  799. </div>
  800. ),
  801. value: () => (
  802. <>
  803. <span
  804. style={{
  805. visibility:
  806. state.platformType === 'TEACHER'
  807. ? 'visible'
  808. : 'hidden'
  809. }}
  810. class={styles.download}
  811. onClick={() => {
  812. if (musicDetail.value?.coursewareStatus) {
  813. removeCourse()
  814. } else {
  815. onAddCourse()
  816. }
  817. }}
  818. >
  819. <img
  820. src={
  821. musicDetail.value?.coursewareStatus
  822. ? iconRemoveCourse
  823. : iconAddCourse
  824. }
  825. />
  826. <span>
  827. {musicDetail.value?.coursewareStatus
  828. ? '移出课件'
  829. : '添加课件'}
  830. </span>
  831. </span>
  832. <span
  833. class={styles.download}
  834. onClick={() => toggleFavorite()}
  835. >
  836. <img
  837. src={
  838. musicDetail.value?.favorite
  839. ? iconCollectActive
  840. : iconCollect
  841. }
  842. />
  843. <span>收藏</span>
  844. </span>
  845. </>
  846. )
  847. }}
  848. />
  849. <div class={styles.functionSection}>
  850. <div
  851. class={styles.functionItem}
  852. onClick={() => {
  853. router.push({
  854. path: '/look-album-list',
  855. query: {
  856. id: musicDetail.value?.id,
  857. musicSubject: musicDetail.value?.musicSubject
  858. }
  859. })
  860. }}
  861. >
  862. <img src={iconAlbum} />
  863. <span>专辑</span>
  864. </div>
  865. {/* {musicDetail.value?.notation ? (
  866. <span
  867. class={styles.download}
  868. onClick={() => {
  869. staff.status = true
  870. }}
  871. style={{
  872. display:
  873. musicDetail.value?.musicSheetType !== 'CONCERT'
  874. ? ''
  875. : 'none'
  876. }}
  877. >
  878. <img src={iconChangeStaff} />
  879. <span>转谱</span>
  880. </span>
  881. ) : null} */}
  882. <div
  883. class={styles.functionItem}
  884. style={{
  885. display:
  886. musicDetail.value?.musicSheetType === 'CONCERT'
  887. ? ''
  888. : 'none'
  889. }}
  890. onClick={() => {
  891. if (musicDetail.value?.musicSheetType === 'CONCERT') {
  892. staffData.open = true
  893. }
  894. }}
  895. >
  896. <img src={iconChange} />
  897. <span>切换乐器</span>
  898. </div>
  899. {musicDetail.value?.notation ? (
  900. <div
  901. class={styles.functionItem}
  902. style={{
  903. display:
  904. musicDetail.value?.musicSheetType === 'SINGLE'
  905. ? ''
  906. : 'none'
  907. }}
  908. onClick={() => {
  909. staff.status = true
  910. }}
  911. >
  912. <img src={iconChange} />
  913. <span>转谱</span>
  914. </div>
  915. ) : null}
  916. <div
  917. class={styles.functionItem}
  918. onClick={() => {
  919. if (musicPdfUrl.value) {
  920. const songName =
  921. musicDetail.value?.musicSheetName +
  922. (staffData.instrumentName
  923. ? `(${staffData.instrumentName})`
  924. : '')
  925. promisefiyPostMessage({
  926. api: 'downloadFile',
  927. content: {
  928. downloadUrl: musicPdfUrl.value,
  929. fileName: songName
  930. }
  931. })
  932. return
  933. }
  934. if (showImg.value.length > 0) {
  935. downloadStatus.value = true
  936. } else {
  937. Toast('暂无图片')
  938. }
  939. }}
  940. >
  941. <img src={iconDownload} />
  942. <span>下载</span>
  943. </div>
  944. </div>
  945. </div>
  946. <div class={styles.musicContent}>
  947. {musicDetail.value?.musicSheetType === 'CONCERT' ||
  948. musicPdfUrl.value ||
  949. !defaultImgs.value[staff.radio] ? (
  950. <>
  951. {musicPdfUrl.value ? (
  952. <iframe
  953. id="staffIframeRef"
  954. style={{
  955. opacity: loading.value ? 0 : 1
  956. }}
  957. src={staffData.iframeSrc}
  958. onLoad={() => {
  959. // 判断是用哪个渲染的
  960. loading.value = false
  961. }}
  962. ></iframe>
  963. ) : (
  964. <>
  965. <p class={styles.musicTitle}>
  966. {(musicDetail.value?.musicSheetName
  967. ? musicDetail.value?.musicSheetName
  968. : '') +
  969. (staffData.instrumentName
  970. ? `(${staffData.instrumentName})`
  971. : '')}
  972. </p>
  973. {loading.value && (
  974. <div>
  975. <Vue3Lottie
  976. animationData={AstronautJSON}
  977. class={styles.finch}
  978. ></Vue3Lottie>
  979. {/* <p class={styles.finchLoad}>加载中...</p> */}
  980. </div>
  981. )}
  982. <iframe
  983. id="staffIframeRef"
  984. style={{
  985. opacity: loading.value ? 0 : 1
  986. }}
  987. // src={staffData.iframeSrc}
  988. onLoad={() => {
  989. musicIframeLoad()
  990. }}
  991. ></iframe>
  992. </>
  993. )}
  994. </>
  995. ) : (
  996. // <>
  997. // {loading.value && (
  998. // <>
  999. // <Vue3Lottie
  1000. // animationData={AstronautJSON}
  1001. // class={styles.finch}
  1002. // ></Vue3Lottie>
  1003. // <p class={styles.finchLoad}>加载中...</p>
  1004. // </>
  1005. // )}
  1006. // <iframe
  1007. // id="staffIframeRef"
  1008. // style={{
  1009. // opacity: loading.value ? 0 : 1
  1010. // }}
  1011. // src={staffData.iframeSrc}
  1012. // onLoad={() => {
  1013. // if (!defaultImgs.value[staff.radio]) {
  1014. // onChangeStaff(staff.radio)
  1015. // } else {
  1016. // musicIframeLoad()
  1017. // }
  1018. // }}
  1019. // ></iframe>
  1020. // </>
  1021. <>
  1022. <p class={styles.musicTitle}>
  1023. {(musicDetail.value?.musicSheetName
  1024. ? musicDetail.value?.musicSheetName
  1025. : '') +
  1026. (staffData.instrumentName
  1027. ? `(${staffData.instrumentName})`
  1028. : '')}
  1029. </p>
  1030. {showImg.value.length > 0 ? (
  1031. <div class={styles.musicImg}>
  1032. <img src={showImg.value[0]} alt="" />
  1033. </div>
  1034. ) : loading.value ? (
  1035. <>
  1036. <Vue3Lottie
  1037. animationData={AstronautJSON}
  1038. class={styles.finch}
  1039. ></Vue3Lottie>
  1040. <p class={styles.finchLoad}>加载中...</p>
  1041. </>
  1042. ) : (
  1043. <div class={styles.empty}>
  1044. <Image src={emtpy} class={styles.emptyImg} />
  1045. <p class={styles.emptyTip}>暂无乐谱预览图</p>
  1046. </div>
  1047. )}
  1048. </>
  1049. )}
  1050. </div>
  1051. </div>
  1052. {musicDetail.value?.id && (
  1053. <ColSticky
  1054. position="bottom"
  1055. background="white"
  1056. varName="--bottom-height"
  1057. >
  1058. <div ref={footers}>
  1059. <div class={styles.videoOperation}>
  1060. {audioFileUrl.value && (
  1061. <>
  1062. {!buyState.value.play &&
  1063. freeRate.value != 100 &&
  1064. freeRate.value != 0 && (
  1065. <div class={[styles.audition]}>
  1066. <img src={iconListen} />
  1067. <span>每首曲目可试听{freeRate.value}%</span>
  1068. </div>
  1069. )}
  1070. <div class={[styles.audio, styles.collectCell]}>
  1071. <audio id="player" controls ref={audio}>
  1072. <source src={audioFileUrl.value} type="audio/mp3" />
  1073. </audio>
  1074. </div>
  1075. </>
  1076. )}
  1077. </div>
  1078. {/* 判断是否是免费的,或者已经购买过,是否从专辑过来的 */}
  1079. {buyState.value.play ||
  1080. (state.platformType === 'TEACHER' &&
  1081. buyState.value.hasTenantAlbum) ? (
  1082. <Button
  1083. round
  1084. block
  1085. type="primary"
  1086. color="linear-gradient(180deg, #59E5D5 0%, #2DC7AA 100%)"
  1087. onClick={() => {
  1088. const throttleFn = useThrottleFn(() => {
  1089. player.value && player.value.stop()
  1090. const item: any = partColumns.value.find(
  1091. (c: any) => c.value === staffData.partIndex
  1092. )
  1093. // const index = staffData.tempPartList.findIndex(
  1094. // (i: any) => i.track === item?.track
  1095. // )
  1096. // 新版云教练的谱面类型使用musicRenderType字段
  1097. const musicRenderType = staff.radio === 'staff' ? 'staff' : staff.radio === 'first' ? 'firstTone' : staff.radio === 'fixed' ? 'fixedTone' : '';
  1098. musicBuy(musicDetail.value, () => {}, {
  1099. 'part-index': item?.xmlValue || 0,
  1100. musicRenderType
  1101. })
  1102. }, 500)
  1103. throttleFn()
  1104. }}
  1105. >
  1106. 立即练习
  1107. </Button>
  1108. ) : (
  1109. <div class={styles.colSticky}>
  1110. {/* 只有,有点播类型的才显示价格 */}
  1111. {buyState.value.charge && (
  1112. <div class={styles.priceSection}>
  1113. <span>点播价:</span>
  1114. <span class={styles.price}>
  1115. <i>¥</i>
  1116. {moneyFormat(musicDetail.value?.musicPrice)}
  1117. </span>
  1118. </div>
  1119. )}
  1120. <div class={[styles.buyBtn]}>
  1121. {/* 判断是否是需要收费的 */}
  1122. {buyState.value.charge && (
  1123. <Button
  1124. round
  1125. type="primary"
  1126. color="linear-gradient(180deg, #59E5D5 0%, #2DC7AA 100%)"
  1127. class={styles.primary}
  1128. onClick={onBuy}
  1129. >
  1130. 立即点播
  1131. </Button>
  1132. )}
  1133. {/* 判断是否有会员的 */}
  1134. {buyState.value.vip && (
  1135. <Button
  1136. round
  1137. block={!buyState.value.charge ? true : false}
  1138. type="primary"
  1139. color="linear-gradient(180deg, #F7BD8D 0%, #CD8806 100%)"
  1140. class={styles.memeber}
  1141. onClick={() => {
  1142. router.push({
  1143. path: '/memberCenter',
  1144. query: {
  1145. ...route.query
  1146. }
  1147. })
  1148. }}
  1149. >
  1150. {studentActivityId.value > 0 && (
  1151. <div class={[styles.buttonDiscount]}>专属优惠</div>
  1152. )}
  1153. 开通会员
  1154. </Button>
  1155. )}
  1156. </div>
  1157. </div>
  1158. )}
  1159. </div>
  1160. </ColSticky>
  1161. )}
  1162. <Popup
  1163. v-model:show={shareStatus.value}
  1164. style={{ background: 'transparent' }}
  1165. teleport="body"
  1166. >
  1167. <ColShare
  1168. teacherId={state.user.data?.userId}
  1169. shareUrl={shareUrl.value}
  1170. shareType="music"
  1171. >
  1172. <div class={styles.shareMate}>
  1173. {shareDiscount.value === 1 && (
  1174. <div class={styles.tagDiscount}>专属优惠</div>
  1175. )}
  1176. <img
  1177. class={styles.icon}
  1178. crossorigin="anonymous"
  1179. src={musicDetail.value?.titleImg + `?t=${+new Date()}`}
  1180. />
  1181. <div class={styles.info}>
  1182. <h4 class="van-multi-ellipsis--l2">
  1183. {musicDetail.value?.musicSheetName}
  1184. </h4>
  1185. <p>作曲人:{musicDetail.value?.composer}</p>
  1186. </div>
  1187. </div>
  1188. </ColShare>
  1189. </Popup>
  1190. <Popup v-model:show={downloadStatus.value} position="bottom" round>
  1191. {downloadStatus.value && (
  1192. <Download
  1193. imgList={JSON.parse(JSON.stringify(showImg.value))}
  1194. musicSheetName={
  1195. musicDetail.value?.musicSheetName +
  1196. (staffData.instrumentName
  1197. ? `(${staffData.instrumentName})`
  1198. : '')
  1199. }
  1200. />
  1201. )}
  1202. </Popup>
  1203. <Popup
  1204. v-model:show={staff.status}
  1205. teleport="body"
  1206. closeable
  1207. style={{ width: '80%' }}
  1208. round
  1209. >
  1210. <div class={styles.staffContainer}>
  1211. <div class={styles.staffTitle}>选择转换曲谱</div>
  1212. <RadioGroup v-model={staff.radio}>
  1213. <CellGroup border={false}>
  1214. <Cell
  1215. center
  1216. border={false}
  1217. class={staff.radio === 'staff' ? styles.active : ''}
  1218. onClick={() => onChangeStaff('staff')}
  1219. >
  1220. {{
  1221. icon: () => (
  1222. <Image src={staffDetafult} class={styles.staffImg} />
  1223. ),
  1224. title: () => <span class={styles.name}>五线谱</span>,
  1225. value: () => (
  1226. <Radio name="staff">
  1227. {{
  1228. icon: (props: any) => (
  1229. <Icon
  1230. class={styles.boxStyle}
  1231. size={16}
  1232. name={
  1233. props.checked
  1234. ? activeButtonIcon
  1235. : inactiveButtonIcon
  1236. }
  1237. />
  1238. )
  1239. }}
  1240. </Radio>
  1241. )
  1242. }}
  1243. </Cell>
  1244. <Cell
  1245. center
  1246. border={false}
  1247. class={staff.radio === 'first' ? styles.active : ''}
  1248. onClick={() => onChangeStaff('first')}
  1249. >
  1250. {{
  1251. icon: () => (
  1252. <Image src={firstDefault} class={styles.staffImg} />
  1253. ),
  1254. title: () => <span class={styles.name}>简谱-首调</span>,
  1255. value: () => (
  1256. <Radio name="first">
  1257. {{
  1258. icon: (props: any) => (
  1259. <Icon
  1260. class={styles.boxStyle}
  1261. size={16}
  1262. name={
  1263. props.checked
  1264. ? activeButtonIcon
  1265. : inactiveButtonIcon
  1266. }
  1267. />
  1268. )
  1269. }}
  1270. </Radio>
  1271. )
  1272. }}
  1273. </Cell>
  1274. <Cell
  1275. center
  1276. border={false}
  1277. class={staff.radio === 'fixed' ? styles.active : ''}
  1278. onClick={() => onChangeStaff('fixed')}
  1279. >
  1280. {{
  1281. icon: () => (
  1282. <Image src={fixedDefault} class={styles.staffImg} />
  1283. ),
  1284. title: () => <span class={styles.name}>简谱-固定调</span>,
  1285. value: () => (
  1286. <Radio name="fixed">
  1287. {{
  1288. icon: (props: any) => (
  1289. <Icon
  1290. class={styles.boxStyle}
  1291. size={16}
  1292. name={
  1293. props.checked
  1294. ? activeButtonIcon
  1295. : inactiveButtonIcon
  1296. }
  1297. />
  1298. )
  1299. }}
  1300. </Radio>
  1301. )
  1302. }}
  1303. </Cell>
  1304. </CellGroup>
  1305. </RadioGroup>
  1306. </div>
  1307. </Popup>
  1308. <Popup
  1309. teleport="body"
  1310. position="bottom"
  1311. round
  1312. v-model:show={staffData.open}
  1313. >
  1314. <Picker
  1315. columns={partColumns.value}
  1316. onConfirm={value => {
  1317. staffData.open = false
  1318. staffData.partIndex = value.value
  1319. staffData.partXmlIndex = value.xmlValue
  1320. staffData.instrumentName = value.instrumentName
  1321. showImg.value = []
  1322. nextTick(() => {
  1323. let tempPdf = value?.musicPdfUrl
  1324. if (musicDetail.value?.musicSheetType !== 'CONCERT') {
  1325. // tempPdf = ''
  1326. if (staff.radio === 'first') {
  1327. tempPdf = value?.firstPdfUrl
  1328. } else if (staff.radio === 'fixed') {
  1329. tempPdf = value?.jianPdfUrl
  1330. } else if (staff.radio === 'staff') {
  1331. tempPdf = value?.musicPdfUrl
  1332. }
  1333. }
  1334. if (tempPdf) {
  1335. musicPdfUrl.value = tempPdf
  1336. renderStaff()
  1337. } else {
  1338. musicPdfUrl.value = ''
  1339. loading.value = true
  1340. // 为了处理,之前是使用pdf渲染,现在又用osmd,iframe没有重新加载
  1341. if (staffData.iframeSrc.indexOf('pdf/web') !== -1) {
  1342. renderStaff()
  1343. } else {
  1344. resetRender()
  1345. }
  1346. }
  1347. })
  1348. }}
  1349. onCancel={() => (staffData.open = false)}
  1350. />
  1351. </Popup>
  1352. </div>
  1353. )
  1354. }
  1355. }
  1356. })